Node.js 安全防护
1. 原型链污染 (Prototype Pollution) 🔥
漏洞原理
攻击者通过恶意 JSON 数据 (包含 __proto__、constructor、prototype) 修改 Object.prototype。
javascript
// 恶意 Payload
const malicious = JSON.parse('{"a": 1, "__proto__": {"isAdmin": true}}');
// 不安全的深度合并
function merge(target, source) {
for (let key in source) {
if (typeof source[key] === 'object') {
target[key] = merge(target[key] || {}, source[key]);
} else {
target[key] = source[key];
}
}
return target;
}
const user = {};
merge(user, malicious);
// 污染成功!
console.log({}.isAdmin); // true (任何对象都有 isAdmin!)2. 严重后果 🔥
逻辑绕过
javascript
function checkPermission(user) {
if (user.isAdmin) { // 从原型链找到 true
return true;
}
return false;
}
const normalUser = {}; // 没有 isAdmin 属性
checkPermission(normalUser); // 被污染后返回 true!拒绝服务 (DoS)
javascript
// 注入循环引用
const payload = '{"__proto__": {"toString": {}}}';
merge({}, JSON.parse(payload));
// 后续所有对象调用 toString 都会报错
const obj = {};
obj.toString(); // TypeError!远程代码执行 (RCE)
结合某些模板引擎或 child_process:
javascript
// 污染 env 或 shell 选项
{"__proto__": {"shell": "/bin/bash", "env": {"NODE_OPTIONS": "--require=./malicious.js"}}}CAUTION
EJS、Pug 等模板引擎曾因原型链污染导致 RCE 漏洞。
3. 防御手段 🔥
方法 1: 使用 Map 代替 Object
javascript
// ✅ Map 没有原型链污染问题
const cache = new Map();
cache.set('key', value);方法 2: 无原型对象
javascript
// ✅ 使用 Object.create(null)
const safeObj = Object.create(null);
safeObj.__proto__ = 'attack'; // 只是普通属性,不会污染
console.log({}.hasOwnProperty); // 仍然正常方法 3: 冻结原型
javascript
// ⚠️ 应用启动时立即执行 (可能导致某些库不兼容)
Object.freeze(Object.prototype);
Object.freeze(Array.prototype);方法 4: 输入过滤
javascript
// ✅ 安全的深度合并
function safeMerge(target, source) {
const FORBIDDEN = ['__proto__', 'constructor', 'prototype'];
for (let key in source) {
if (FORBIDDEN.includes(key)) continue; // 过滤危险键
if (typeof source[key] === 'object' && source[key] !== null) {
target[key] = safeMerge(target[key] || {}, source[key]);
} else {
target[key] = source[key];
}
}
return target;
}方法 5: 使用安全的库
javascript
// ✅ lodash 4.17.12+ 已修复
const _ = require('lodash');
// 确保使用最新版本
// ✅ 或使用 @hapi/hoek
const Hoek = require('@hapi/hoek');
Hoek.merge(target, source);4. 检测工具
bash
# npm audit
npm audit
# snyk
npx snyk test自动化检测
javascript
// 检测是否被污染
function isPrototypePolluted() {
const test = {};
return test.polluted !== undefined;
}
// 定期检查
setInterval(() => {
if (isPrototypePolluted()) {
console.error('Prototype pollution detected!');
process.exit(1);
}
}, 60000);