Node.js 原型污染攻击的分析与利用
字数 1283 2025-08-26 22:11:22

Node.js 原型污染攻击分析与防御指南

1. JavaScript原型基础

1.1 JavaScript对象模型

JavaScript中的对象是键值对的集合,每个键值对称为属性:

var obj = {
  "name": "0daylabs",
  "website": "blog.0daylabs.com"
};

即使我们只定义了namewebsite两个属性,对象实际上包含更多信息,这些额外信息来自原型链。

1.2 原型链机制

  • Object是所有对象的基类
  • 可以通过Object.create(null)创建真正的空对象(无原型)
  • 每个对象都有__proto__属性指向其构造函数的prototype

1.3 构造函数与原型

JavaScript中函数同时充当构造函数:

function Person(fullName, age) {
  this.age = age;
  this.fullName = fullName;
}

var person1 = new Person("Anirudh", 25);

关键点:

  • 函数创建时自动获得prototype属性
  • 对象创建时自动获得__proto__属性指向构造函数的prototype
  • constructor属性指向创建该对象的函数

2. 原型污染原理

2.1 原型动态修改

可以在运行时修改原型属性:

Person.prototype.details = function() {
  return this.fullName + " has age: " + this.age;
}

或者通过对象修改:

person1.constructor.prototype.details = function() {
  return this.fullName + " has age: " + this.age;
}

2.2 污染攻击向量

当存在以下操作时可能发生原型污染:

  1. 对象递归合并
  2. 按路径定义属性
  3. 对象克隆

攻击模式:

obj[a][b] = value

如果攻击者控制avalue,可以将a设为__proto__,从而为所有对象定义属性b

3. 实际攻击案例分析

3.1 漏洞代码示例

function merge(a, b) {
  for (var attr in b) {
    if (isObject(a[attr]) && isObject(b[attr])) {
      merge(a[attr], b[attr]);
    } else {
      a[attr] = b[attr];
    }
  }
  return a
}

function clone(a) {
  return merge({}, a);
}

3.2 攻击过程

  1. 发送恶意JSON负载:
{"__proto__": {"admin": 1}}
  1. 通过clone()merge()处理时:
  • 遍历属性发现__proto__
  • 递归处理a[__proto__]b[__proto__]
  • 实际上修改了Object的原型,添加了admin属性
  1. 所有对象现在都具有admin: 1属性

3.3 完整攻击链

curl -vv --header 'Content-type: application/json' -d '{"__proto__": {"admin": 1}}' 'http://target/signup'
curl -vv 'http://target/getFlag'

4. 漏洞防御措施

4.1 安全的对象合并

  1. 使用现代JavaScript特性:
Object.assign({}, a, b)
  1. 使用安全的合并库(如lodash的_.mergeWith

  2. 实现安全的合并函数:

function safeMerge(target, source) {
  Object.keys(source).forEach(key => {
    if (key === '__proto__' || key === 'constructor') {
      return;
    }
    if (isObject(source[key]) && isObject(target[key])) {
      safeMerge(target[key], source[key]);
    } else {
      target[key] = source[key];
    }
  });
  return target;
}

4.2 其他防御策略

  1. 冻结原型:
Object.freeze(Object.prototype);
  1. 使用无原型对象:
const safeObj = Object.create(null);
  1. 输入验证:
  • 检查JSON输入是否包含__proto__constructor属性
  • 使用schema验证库
  1. 使用最新版本的库:
  • 许多库已修复原型污染问题

5. 深入技术细节

5.1 原型链遍历过程

当访问对象属性时:

  1. 检查对象自身属性
  2. 如果不存在,通过__proto__查找原型链
  3. 递归直到Object.prototype或找到属性

5.2 污染影响范围

原型污染可以影响:

  • 应用中的所有对象
  • 第三方库创建的对象
  • Node.js核心对象(如global

5.3 可能导致的安全问题

  1. 权限绕过(如示例中的admin检查)
  2. 拒绝服务(覆盖关键函数)
  3. 远程代码执行(污染后利用)

6. 实际开发建议

  1. 始终验证和净化用户输入的JSON
  2. 避免使用不安全的递归合并
  3. 考虑使用TypeScript增强类型安全
  4. 定期审计依赖库的安全性
  5. 实施严格的CSP策略

7. 相关资源

  1. Olivier Arteau的NorthSec 2018演讲
  2. JavaScript原型详解
  3. MDN原型文档
  4. 0daylabs原型污染文章
Node.js 原型污染攻击分析与防御指南 1. JavaScript原型基础 1.1 JavaScript对象模型 JavaScript中的对象是键值对的集合,每个键值对称为属性: 即使我们只定义了 name 和 website 两个属性,对象实际上包含更多信息,这些额外信息来自原型链。 1.2 原型链机制 Object 是所有对象的基类 可以通过 Object.create(null) 创建真正的空对象(无原型) 每个对象都有 __proto__ 属性指向其构造函数的 prototype 1.3 构造函数与原型 JavaScript中函数同时充当构造函数: 关键点: 函数创建时自动获得 prototype 属性 对象创建时自动获得 __proto__ 属性指向构造函数的 prototype constructor 属性指向创建该对象的函数 2. 原型污染原理 2.1 原型动态修改 可以在运行时修改原型属性: 或者通过对象修改: 2.2 污染攻击向量 当存在以下操作时可能发生原型污染: 对象递归合并 按路径定义属性 对象克隆 攻击模式: 如果攻击者控制 a 和 value ,可以将 a 设为 __proto__ ,从而为所有对象定义属性 b 。 3. 实际攻击案例分析 3.1 漏洞代码示例 3.2 攻击过程 发送恶意JSON负载: 通过 clone() 或 merge() 处理时: 遍历属性发现 __proto__ 递归处理 a[__proto__] 和 b[__proto__] 实际上修改了 Object 的原型,添加了 admin 属性 所有对象现在都具有 admin: 1 属性 3.3 完整攻击链 4. 漏洞防御措施 4.1 安全的对象合并 使用现代JavaScript特性: 使用安全的合并库(如lodash的 _.mergeWith ) 实现安全的合并函数: 4.2 其他防御策略 冻结原型: 使用无原型对象: 输入验证: 检查JSON输入是否包含 __proto__ 或 constructor 属性 使用schema验证库 使用最新版本的库: 许多库已修复原型污染问题 5. 深入技术细节 5.1 原型链遍历过程 当访问对象属性时: 检查对象自身属性 如果不存在,通过 __proto__ 查找原型链 递归直到 Object.prototype 或找到属性 5.2 污染影响范围 原型污染可以影响: 应用中的所有对象 第三方库创建的对象 Node.js核心对象(如 global ) 5.3 可能导致的安全问题 权限绕过(如示例中的admin检查) 拒绝服务(覆盖关键函数) 远程代码执行(污染后利用) 6. 实际开发建议 始终验证和净化用户输入的JSON 避免使用不安全的递归合并 考虑使用TypeScript增强类型安全 定期审计依赖库的安全性 实施严格的CSP策略 7. 相关资源 Olivier Arteau的NorthSec 2018演讲 JavaScript原型详解 MDN原型文档 0daylabs原型污染文章