在JavaScript中,原型链污染(Prototype Pollution)是一种安全漏洞,攻击者通过修改对象的原型属性来影响整个应用程序的行为。这种攻击利用了JavaScript的原型继承机制,当对象在原型链上查找属性时,如果找不到就会沿着原型链向上查找。
javascript
// 一个简单的原型链污染示例
const obj = {};
obj.__proto__.isAdmin = true; // 污染所有对象的原型
const user = {};
console.log(user.isAdmin); // true,尽管user对象本身没有这个属性
原型链污染的危害
原型链污染可能导致以下安全问题:
- 权限提升:攻击者可能通过污染原型链来添加管理员权限标志
- 拒绝服务:修改内置方法可能导致应用程序崩溃
- 远程代码执行:在某些情况下可能导致更严重的代码执行漏洞
- 数据篡改:影响应用程序的数据处理和验证逻辑
常见的污染途径
- 不安全的对象合并:
javascript
function merge(target, source) {
for (let key in source) {
if (typeof target[key] === 'object' && typeof source[key] === 'object') {
merge(target[key], source[key]);
} else {
target[key] = source[key];
}
}
}
// 攻击者可以传入包含__proto__属性的对象
- 不安全的对象复制:
javascript
const obj = JSON.parse('{"__proto__": {"isAdmin": true}}');
// 在某些JavaScript引擎中会污染原型
- 使用用户控制的属性名:
javascript
function setProperty(obj, key, value) {
obj[key] = value; // 如果key是"__proto__"就可能造成污染
}
防护措施
1. 使用Object.create(null)创建无原型对象
javascript
const safeObj = Object.create(null); // 这个对象没有原型链
safeObj.__proto__ = {isAdmin: true}; // 无效,因为safeObj没有原型链
2. 冻结Object.prototype
javascript
Object.freeze(Object.prototype);
// 现在尝试修改原型会被阻止
Object.prototype.isAdmin = true; // 在严格模式下会抛出TypeError
3. 安全的对象合并实现
javascript
function safeMerge(target, source) {
for (const key in source) {
if (key === '__proto__' || key === 'constructor') {
continue; // 跳过敏感属性
}
if (typeof target[key] === 'object' && typeof source[key] === 'object') {
safeMerge(target[key], source[key]);
} else {
target[key] = value;
}
}
}
4. 使用Map代替普通对象
javascript
const safeMap = new Map();
safeMap.set('isAdmin', true); // 不会影响原型链
5. 输入验证和过滤
javascript
function sanitizeKey(key) {
return key.replace(/__proto__|constructor|prototype/gi, '');
}
6. 使用现代JavaScript特性
javascript
// 使用Object.hasOwn()替代in操作符检查属性
if (Object.hasOwn(obj, key)) {
// 安全处理
}
框架和库中的防护
许多现代JavaScript框架和库已经内置了防护措施:
- Lodash:从v4.17.15开始修复了merge函数的原型污染问题
- jQuery:在较新版本中修复了extend方法的原型污染问题
- Express:body-parser中间件默认会过滤掉__proto__属性
检测原型链污染
可以使用以下方法检测潜在的原型链污染:
javascript
function checkPrototypePollution() {
const testObj = {};
try {
testObj.__proto__.__proto__ = {polluted: true};
return testObj.polluted === true;
} catch (e) {
return false;
}
}
总结
原型链污染是JavaScript应用中一个严重但常被忽视的安全问题。通过理解其工作原理和采用适当的防护措施,开发者可以显著降低应用的安全风险。关键是要始终验证和清理用户输入,使用安全的对象操作方法,并保持依赖库的更新以获取最新的安全修复。
在开发过程中,应该将安全防护作为代码审查的一部分,并使用自动化工具如ESLint插件来检测潜在的原型链污染漏洞。