JavaScript原型链污染探讨
字数 1086 2025-08-20 18:18:04

JavaScript原型链污染详解

一、基本概念

1.1 __proto__prototype的区别

  • __proto__:

    • 每个对象都有的隐藏属性
    • 指向创建该对象的构造函数的原型对象(prototype)
    • 是对象用于继承属性和方法的机制
    • 所有对象都可以通过__proto__访问其原型,实现原型链查找
  • prototype:

    • 是函数(特别是构造函数)特有的属性
    • 用于定义由该构造函数创建的实例共享的属性和方法
    • 通常用来定义构造函数的实例方法
    • 新创建的对象会继承其构造函数prototype上的属性和方法
const obj = {};
console.log(obj.__proto__ === Object.prototype); // true

1.2 原型链机制

JavaScript中的继承是通过原型链实现的:

  • 每个对象都有一个指向其原型的内部链接(__proto__)
  • 原型本身也是一个对象,通常还有自己的原型
  • 形成一条原型链
  • 访问对象属性时,JavaScript会沿着原型链逐级查找

二、原型链示例

function Animal(name) {
  this.name = name;
}

function Something() {
  this.speak = function() {
    console.log(this.name + ' makes a beautiful sound.');
  };
};

const dog = new Animal('Dog');
dog.speak(); // 输出: 'Dog makes a beautiful sound.'

原型链关系:

dog -> Animal.prototype -> Something.prototype -> Object.prototype -> null

三、原型链污染原理

通过修改对象的原型(__proto__),可以影响所有与该对象来自同一个类、父类直到Object类的对象。

3.1 污染示例

function Animal(name) {
  this.name = name;
}

function Something() {
  this.speak = function() {
    console.log(this.name + ' makes a beautiful sound.');
  };
};

const dog = new Animal('Dog');
dog.__proto__.speak = function() {
  console.log('修改成功');
};
dog.speak(); // 输出: '修改成功'

3.2 典型污染场景:merge操作

function merge(target, source) {
  for (let key in source) {
    if (key in source && key in target) {
      merge(target[key], source[key])
    } else {
      target[key] = source[key]
    }
  }
}

当攻击者可以控制source对象时,可以通过设置__proto__属性来污染原型链:

let obj = {};
merge(obj, {
  __proto__: {
    polluted: true
  }
});
// 现在所有对象的原型都被污染了
console.log({}.polluted); // true

四、防御措施

  1. 避免使用__proto__:

    • 使用Object.getPrototypeOf()Object.setPrototypeOf()替代
  2. 安全的对象合并:

    • 检查属性名是否为__proto__constructor等敏感属性
    • 使用Object.hasOwnProperty()确保只处理对象自身的属性
  3. 冻结原型:

    • 使用Object.freeze(Object.prototype)防止原型被修改
  4. 使用Map代替Object:

    • Map不会受到原型链污染的影响
  5. 输入验证:

    • 严格验证用户输入,特别是用于构建对象的JSON数据

五、实际应用场景

  1. Node.js应用:

    • 污染全局对象原型可能导致所有模块受到影响
  2. 前端框架:

    • 污染可能导致不可预测的行为或XSS漏洞
  3. API处理:

    • 不安全的JSON解析可能导致原型链污染

六、检测方法

  1. 检查对象原型:

    console.log(Object.prototype.polluted); // 检查是否被污染
    
  2. 使用安全模式:

    • 启用严格模式('use strict')可以帮助发现一些问题
  3. 代码审计:

    • 检查所有对象合并、复制操作的安全性

理解原型链污染对于JavaScript开发者至关重要,特别是在构建需要处理不可信输入的应用程序时。通过遵循安全实践和防御措施,可以有效防止这类安全问题。

JavaScript原型链污染详解 一、基本概念 1.1 __proto__ 与 prototype 的区别 __proto__ : 每个对象都有的隐藏属性 指向创建该对象的构造函数的原型对象( prototype ) 是对象用于继承属性和方法的机制 所有对象都可以通过 __proto__ 访问其原型,实现原型链查找 prototype : 是函数(特别是构造函数)特有的属性 用于定义由该构造函数创建的实例共享的属性和方法 通常用来定义构造函数的实例方法 新创建的对象会继承其构造函数 prototype 上的属性和方法 1.2 原型链机制 JavaScript中的继承是通过原型链实现的: 每个对象都有一个指向其原型的内部链接( __proto__ ) 原型本身也是一个对象,通常还有自己的原型 形成一条原型链 访问对象属性时,JavaScript会沿着原型链逐级查找 二、原型链示例 原型链关系: 三、原型链污染原理 通过修改对象的原型( __proto__ ),可以影响所有与该对象来自同一个类、父类直到Object类的对象。 3.1 污染示例 3.2 典型污染场景:merge操作 当攻击者可以控制 source 对象时,可以通过设置 __proto__ 属性来污染原型链: 四、防御措施 避免使用 __proto__ : 使用 Object.getPrototypeOf() 和 Object.setPrototypeOf() 替代 安全的对象合并 : 检查属性名是否为 __proto__ 或 constructor 等敏感属性 使用 Object.hasOwnProperty() 确保只处理对象自身的属性 冻结原型 : 使用 Object.freeze(Object.prototype) 防止原型被修改 使用Map代替Object : Map不会受到原型链污染的影响 输入验证 : 严格验证用户输入,特别是用于构建对象的JSON数据 五、实际应用场景 Node.js应用 : 污染全局对象原型可能导致所有模块受到影响 前端框架 : 污染可能导致不可预测的行为或XSS漏洞 API处理 : 不安全的JSON解析可能导致原型链污染 六、检测方法 检查对象原型 : 使用安全模式 : 启用严格模式( 'use strict' )可以帮助发现一些问题 代码审计 : 检查所有对象合并、复制操作的安全性 理解原型链污染对于JavaScript开发者至关重要,特别是在构建需要处理不可信输入的应用程序时。通过遵循安全实践和防御措施,可以有效防止这类安全问题。