原型链污染(Prototype Pollution)是一种针对 JavaScript 应用的安全漏洞,攻击者通过操纵对象的原型链,向基础对象(如 Object.prototype)注入恶意属性,从而影响整个应用程序的行为。以下是详细解析:
核心原理
-
JavaScript 原型链机制:
- 每个对象都有隐式原型
__proto__(或通过Object.getPrototypeOf()访问),指向其构造函数的原型对象。 - 访问对象属性时,若自身不存在,会沿原型链向上查找(直到
Object.prototype)。 - 修改原型对象的属性会影响所有继承该原型的对象。
- 每个对象都有隐式原型
-
污染触发条件:
- 当应用递归合并 用户输入的 JSON 数据(如
Object.assign()或自定义 merge 函数)。 - 通过路径赋值 动态设置属性(如
obj[a][b] = value)。 - 攻击者构造包含
__proto__或constructor/prototype的恶意 payload。
- 当应用递归合并 用户输入的 JSON 数据(如
攻击示例
场景1:递归合并对象
javascript
function merge(target, source) {
for (const key in source) {
if (typeof source[key] === 'object') {
if (!target[key]) target[key] = {};
merge(target[key], source[key]); // 递归合并
} else {
target[key] = source[key]; // 直接赋值
}
}
}
const userInput = JSON.parse('{"__proto__": {"isAdmin": true}}');
merge({}, userInput); // 污染 Object.prototype
// 验证
const obj = {};
console.log(obj.isAdmin); // true!所有对象继承恶意属性
场景2:路径赋值
javascript
function setValue(obj, path, value) {
const keys = path.split('.');
let current = obj;
for (let i = 0; i < keys.length - 1; i++) {
if (!current[keys[i]]) current[keys[i]] = {};
current = current[keys[i]];
}
current[keys[keys.length - 1]] = value;
}
// 攻击者输入路径 "__proto__.isAdmin" 和值 true
setValue({}, "__proto__.isAdmin", true);
// 验证
console.log(({}).isAdmin); // true
危害
-
权限提升 :添加
isAdmin: true属性绕过身份检查。 -
拒绝服务(DoS) :覆盖
toString()等方法导致应用崩溃。 -
远程代码执行(RCE) :结合其他漏洞(如模板注入)执行任意代码。
javascript// 若应用使用 eval 或类似功能 Object.prototype.shell = "require('child_process').exec('rm -rf /')";
防御措施
-
避免原型属性操作 :
-
使用
Object.create(null)创建无原型的对象。 -
检查属性名是否为
__proto__、constructor、prototype:javascriptif (key.includes("__proto__") || key.includes("constructor")) return;
-
-
安全合并对象 :
- 使用
Object.assign()(浅拷贝,不递归)替代自定义 merge。 - 使用三方库如
lodash.merge(其新版已修复污染问题)。
- 使用
-
冻结原型 :
javascriptObject.freeze(Object.prototype); // 禁止修改原型 -
使用 Map 替代 Object :
Map不受原型链影响。 -
输入过滤 :
- 对用户输入的 JSON 数据过滤敏感键名。
- 使用
JSON.parse()替代eval()。
真实案例
- Lodash(CVE-2018-3721) :旧版
_.defaultsDeep存在污染漏洞。 - jQuery(CVE-2019-11358) :
$.extend(true, {}, payload)可被利用。 - Mongoose:未验证查询参数导致污染。
检测工具
- Scanner :pp-finder
- Chrome DevTools :检查
Object.prototype是否有异常属性。
总结:原型链污染源于 JavaScript 的动态原型机制,通过严格控制对象操作、避免递归处理用户数据、冻结原型或使用无原型对象,可有效防御此漏洞。