JS原型污染漏洞

约 576 字 约 2 分钟阅读

2025-12-24 JS原型污染漏洞

原型污染(Prototype Pollution)是 JavaScript 里一类隐蔽但危害很大的漏洞,处理不可信输入时尤其容易中招。被利用后可能导致权限提升、任意代码执行、拒绝服务(DoS)等后果。

原型污染的基本原理

JavaScript 中几乎所有对象都继承自 Object.prototype,而原型可以通过 __proto__prototypeconstructor 这几个特殊属性访问和修改。

如果往一个普通对象上动态添加属性时,键名恰好是 __proto__,改动就会落到原型对象上,整条原型链随之被污染。

简单演示

// 正常情况:给空对象添加属性
const user = {};
user.name = 'Alice';
console.log(user.name); // 'Alice'

// 原型污染攻击
const maliciousInput = JSON.parse('{"__proto__": {"isAdmin": true}}');

// 假设有一个不安全的深层合并函数(常见于许多库)
function merge(target, source) {
  for (const key in source) {
    target[key] = source[key];
  }
  return target;
}

const cleanObj = {};
merge(cleanObj, maliciousInput);

// 现在所有普通对象的原型都被污染了!
console.log({}.isAdmin);        // true  ← 危险!任意空对象都有 isAdmin 属性
console.log(user.isAdmin);      // true
console.log((new Date()).isAdmin); // true,甚至原生对象也可能受影响

攻击者只要让输入对象里出现 __proto__ 这样的键,就可能往 Object.prototype 上注入属性,之后创建的所有对象都会受影响。

常见的特殊键

以下键都会影响原型链(统称为污染入口):

  • __proto__ ← 最常见、最直接
  • prototype ← 常用于污染构造函数的原型
  • constructor ← 可间接访问 prototype
// 三种等效的污染方式
obj['__proto__'].polluted = true;
obj.constructor.prototype.polluted = true;
obj['constructor']['prototype'].polluted = true;

// 结果都是:Object.prototype.polluted === true

真实攻击场景举例

  1. 权限绕过

    // 服务端校验用户权限
    function isAdmin(user) {
      return user.isAdmin === true;  // 本应从数据库读取
    }
    
    // 攻击者提交 { "__proto__": { "isAdmin": true } }
    // 导致所有对象默认都有 isAdmin: true → 权限绕过成功
    
  2. 拒绝服务(DoS)

    // 注入一个会导致无限循环或崩溃的 getter
    {"__proto__": {"toString": () => { while(true) {} }}}
    
  3. 结合其他漏洞实现 RCE(远程代码执行) 在某些 Node.js 环境中,污染 child_processfs 相关原型后,可能配合其他 gadget 实现任意命令执行。历史上 lodash(< 4.17.11)等多个知名库都中过招。

如何防御原型污染

  1. 避免直接使用用户输入作为对象键进行递归合并

    • 不要自己写深层 merge 函数
    • 使用安全的库或手动控制合并逻辑
  2. 严格校验键名

    function safeAssign(target, key, value) {
      if (key === '__proto__' || key === 'prototype' || key === 'constructor') {
        throw new Error('Forbidden property name');
      }
      target[key] = value;
    }
    
  3. 使用 Object.create(null) 创建无原型对象

    const map = Object.create(null);  // 没有 __proto__ 属性,完全安全
    map[userInputKey] = value;        // 即使 key 是 "__proto__" 也只是普通键
    
  4. 冻结原型(生产环境可考虑)

    Object.freeze(Object.prototype);
    // 尝试污染会静默失败(非严格模式)或抛错(严格模式)
    
  5. 升级依赖库 许多知名库(如 lodash、jquery、express 等)都修复过原型污染漏洞,保持依赖最新非常重要。

小结

原型污染的本质,是攻击者通过控制键名改掉了所有对象共享的原型。

它听起来冷门,实际上离日常开发很近,这几类场景要特别当心:

  • 接收 JSON 输入的 API
  • 做深层对象合并的配置系统
  • 停留在旧版本、未修复漏洞的第三方库

防御原则一句话:外部输入不能直接拿来当对象属性键,递归合并时尤其如此。

javascript安全漏洞

评论讨论