在 JavaScript 中,原型污染(Prototype Pollution) 是一种通过修改对象的原型链,向所有继承该原型的对象注入或覆盖属性的安全漏洞。这种操作可能导致代码行为异常、数据篡改甚至远程代码执行(RCE)等风险。
原型污染的原理
JavaScript 的原型链继承机制允许对象通过 __proto__
(或 Object.getPrototypeOf()
)访问原型对象。如果攻击者能修改原型对象的属性(如 Object.prototype
),所有继承该原型的对象都会继承这些修改。
示例:原型污染的产生
javascript
// 直接修改 Object.prototype
Object.prototype.isAdmin = true;
const obj = {};
console.log(obj.isAdmin); // true(原型链被污染)
如何防止原型污染?
1. 避免直接操作 __proto__
或原型
-
关键点:禁止外部数据直接修改对象的原型链。
-
代码示例 :
javascript// 安全做法:避免动态设置 __proto__ function safeMerge(target, source) { for (const key in source) { if (key === '__proto__' || key === 'constructor') { continue; // 忽略敏感属性 } target[key] = source[key]; } }
2. 使用 Object.create(null)
创建无原型对象
-
关键点 :创建的对象不继承
Object.prototype
,天然免疫原型污染。 -
代码示例 :
javascriptconst safeObj = Object.create(null); // 无原型链 safeObj.name = "Safe"; console.log(safeObj.toString); // undefined(无法访问 Object.prototype 的方法)
3. 冻结原型对象
-
关键点 :使用
Object.freeze()
禁止修改原型。 -
代码示例 :
javascript// 冻结 Object.prototype Object.freeze(Object.prototype); // 尝试污染原型会失败(严格模式下报错) Object.prototype.isAdmin = true; console.log(Object.prototype.isAdmin); // undefined
4. 安全处理对象合并操作
-
关键点 :在合并对象属性时,过滤敏感键(如
__proto__
、constructor
、prototype
)。 -
代码示例 :
javascriptfunction safeAssign(target, source) { Object.keys(source).forEach(key => { if (key in Object.prototype) return; // 忽略原型上的键 target[key] = source[key]; }); }
5. 使用 Map 替代普通对象
-
关键点 :
Map
的键可以是任意类型,且不会继承原型链属性。 -
代码示例 :
javascriptconst map = new Map(); map.set("isAdmin", true); console.log(map.get("isAdmin")); // true console.log(map.isAdmin); // undefined(无法通过原型链污染)
6. 使用安全的第三方库
-
关键点 :避免手写不安全的对象操作,选择经过安全审计的库(如 Lodash 的
_.merge
默认过滤原型键)。 -
代码示例 :
javascriptconst _ = require('lodash'); const safeObj = _.merge({}, maliciousPayload); // 自动忽略 __proto__
7. 启用严格模式(Strict Mode)
-
关键点:严格模式下,修改只读属性(如冻结后的原型)会抛出错误。
-
代码示例 :
javascript'use strict'; Object.prototype.isAdmin = true; // 报错:Cannot add property isAdmin
原型污染的常见攻击场景
-
解析不安全的 JSON 数据 :
javascriptconst data = JSON.parse('{ "__proto__": { "isAdmin": true } }'); // 可能污染原型链
-
用户输入合并到对象 :
如 HTTP 请求参数、表单数据等未经验证直接合并到对象中。 -
第三方库漏洞 :
未正确处理用户输入的库可能成为污染入口。
总结
防御方法 | 适用场景 | 优点 |
---|---|---|
过滤敏感键 | 对象合并、深拷贝 | 直接阻断污染路径 |
使用 Object.create(null) |
高频操作或敏感数据存储 | 彻底免疫原型链污染 |
冻结原型 | 全局防护 | 防止所有原型修改 |
使用 Map |
键值对存储 | 无原型链,天然安全 |
核心原则:
- 永远不要信任外部输入,始终验证和清理数据。
- 最小化原型操作,优先使用无原型对象或安全数据结构。