---
author: QinIndexCode
tags: JavaScript, 安全, 漏洞
---

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

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

### 原型污染的基本原理

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

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

#### 简单演示

```javascript
// 正常情况：给空对象添加属性
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

```javascript
// 三种等效的污染方式
obj['__proto__'].polluted = true;
obj.constructor.prototype.polluted = true;
obj['constructor']['prototype'].polluted = true;

// 结果都是：Object.prototype.polluted === true
```

### 真实攻击场景举例

1. **权限绕过**
   ```javascript
   // 服务端校验用户权限
   function isAdmin(user) {
     return user.isAdmin === true;  // 本应从数据库读取
   }

   // 攻击者提交 { "__proto__": { "isAdmin": true } }
   // 导致所有对象默认都有 isAdmin: true → 权限绕过成功
   ```

2. **拒绝服务（DoS）**
   ```javascript
   // 注入一个会导致无限循环或崩溃的 getter
   {"__proto__": {"toString": () => { while(true) {} }}}
   ```

3. **结合其他漏洞实现 RCE（远程代码执行）**
   在某些 Node.js 环境中，污染 `child_process` 或 `fs` 相关原型后，可能配合其他 gadget 实现任意命令执行。历史上 lodash（< 4.17.11）等多个知名库都中过招。

### 如何防御原型污染

1. **避免直接使用用户输入作为对象键进行递归合并**
   - 不要自己写深层 merge 函数
   - 使用安全的库或手动控制合并逻辑

2. **严格校验键名**
   ```javascript
   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) 创建无原型对象**
   ```javascript
   const map = Object.create(null);  // 没有 __proto__ 属性，完全安全
   map[userInputKey] = value;        // 即使 key 是 "__proto__" 也只是普通键
   ```

4. **冻结原型（生产环境可考虑）**
   ```javascript
   Object.freeze(Object.prototype);
   // 尝试污染会静默失败（非严格模式）或抛错（严格模式）
   ```

5. **升级依赖库**
   许多知名库（如 lodash、jquery、express 等）都修复过原型污染漏洞，保持依赖最新非常重要。

### 小结

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

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

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

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