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") } 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一下看看有没有组件的漏洞
可以看到这里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
|
简单的调试一下就可以看到
污染了他的父类,获取的时候获取到了相关的属性
分析
网上没找到相关的文章,自己分析看看贴一下源码
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中的数组取出来,通过递归,将每一个键值和属性取出来,构造成一个对象
注意到修改后的函数里面加了一句特判
如果没有这句特判
__proto__会被作为一个键值塞入到构造的对象中,解析这个对象的时候就会造成原型链污染