周末刚好没有太多的事情,参加了 Google CTF 来玩玩,印象中是第一次参加这个比赛。总的来说 google 题目还是属于质量不错的一类,但是 web 题目难度梯度太大了,前面二道还可以接受,但是最后二道好像到比赛结束都是零解,佛了 ~ 佛了 ~
通过阅读本文章,你将学会:
- blind XXE by local dtd
- SQL injection by order with side channels
- 组合数学
bnv
题目链接是:https://bnv.web.ctfcompetition.com/
访问主页会看到:
查看源代码看到有一个 post.js 文件,访问可以得到重要的 javascript 代码:
function AjaxFormPost() {
var datasend;
var message = document.getElementById('message').value;
message = message.toLowerCase();
var blindvalues = [
'10', '120', '140', '1450', '150', '1240', '12450',
'1250', '240', '2450', '130', '1230', '1340', '13450',
'1350', '12340', '123450', '12350', '2340', '23450', '1360',
'12360', '24560', '13460', '134560', '13560',
];
var blindmap = new Map();
var i;
var message_new = '';
for (i = 0; i < blindvalues.length; i++) {
blindmap[i + 97] = blindvalues[i];
}
for (i = 0; i < message.length; i++) {
message_new += blindmap[(message[i].charCodeAt(0))];
}
datasend = JSON.stringify({
'message': message_new,
});
var url = '/api/search';
xhr = new XMLHttpRequest();
xhr.open('POST', url, true);
xhr.setRequestHeader('Content-type', 'application/json');
xhr.onreadystatechange =
function() {
if (xhr.readyState == 4 && xhr.status == 200) {
console.log(xhr.getResponseHeader('Content-Type'));
if (xhr.getResponseHeader('Content-Type') == "application/json; charset=utf-8") {
try {
var json = JSON.parse(xhr.responseText);
document.getElementById('database-data').value = json['ValueSearch'];
}
catch(e) {;
document.getElementById('database-data').value = e.message;
}
}
else {
document.getElementById('database-data').value = xhr.responseText;
}
}
}
xhr.send(datasend);
}
这段代码就是把 City 输入框的值每个字母转换为小写再映射到 长度为 26 的 blindvalue 数组对应位置的数字,然后将这串数字发送到 api/search
后端,Content-type
类型是 application/json
,如果使用 Burp 截取数据包如下:
首先,思考这个命名为 blindvalue 的数组是什么含义,为什么需要这么映射?联想主页的标题有许多点,我很快就意识到这可能是个盲文,果然通过搜索就找到这就是 布莱叶盲文 。
然后对此我编写一个 py 脚本用于将小写字母映射到 blindvalue 数字:
# encoding:utf-8
blindvalues = [
'10', '120', '140', '1450', '150', '1240', '12450',
'1250', '240', '2450', '130', '1230', '1340', '13450',
'1350', '12340', '123450', '12350', '2340', '23450', '1360',
'12360', '24560', '13460', '134560', '13560',
]
msg = "Paris"
msg = msg.lower()
blindmap = {}
new = ""
for i in range(len(blindvalues)):
blindmap[i + 97] = blindvalues[i]
for j in range(len(msg)):
new += blindmap[ord(msg[j])]
print(new)
到这一步基本把题目的信息理解清楚了,接下来我考虑到几种思路:
盲文是否支持其他字符的映射?
不提交数字会引发服务器什么问题,是否存在注入等问题?
观察主页的 city 选项框的选项,全是 google 公司所在的城市(Zurish、Paris、Bangalore),题目的描述是:
Please use the search engine below to find the closest association near you.
是否尝试提交其他城市的 message 可以获得 flag?
是否有其他可以提交的 json 健值对?
接下里的几个小时就是对这些思路的验证,非常遗憾没有一个能让我利用,也不存在其他的 json 键值对,并且服务器只允许提交这 Zurish、Paris、Bangalore 三个城市的盲文数字。在这过程中也有一些收获,比如:
- 知道目标服务器是 wsgi + flask 模式
- 后端使用 json.loads 解码 JSON 字符串
- 知道了 google 在全球的办公地点。。
后来突然想到既然是 application/json
那么是否支持 XML 呢?于是尝试了修改 Content-type
类型为 application/xml
,竟然成功!下图返回结果说明服务端试图解析 XML 数据!
尝试网上公开的各种 XML 相关的利用: