JavaScript 原型链与原型链污染全解析

发布于:2025-03-05 ⋅ 阅读:(16) ⋅ 点赞:(0)

JavaScript 原型链与原型链污染全解析


一、JavaScript 对象与原型基础

1. 对象的本质
  • 键值对容器:对象是动态的键值对集合,属性值可为任意类型(包括函数)。
  • 引用类型:赋值传递内存地址,多个变量可共享同一对象。
  • 动态性:支持随时增删属性:
    const obj = { a: 1 };
    obj.b = 2;      // 添加属性
    delete obj.a;   // 删除属性
    
2. 原型(Prototype)
  • 隐藏属性:每个对象都有一个 [[Prototype]](通过 __proto__Object.getPrototypeOf() 访问)。
  • 默认原型
    • 普通对象的原型默认指向 Object.prototype
    • Object.prototype.__proto__ === null(原型链终点)。

二、原型链的形成与查找机制

1. 原型链结构
实例对象 → 构造函数原型 → Object.prototype → null
2. 属性查找规则
  1. 检查对象自身属性。
  2. 若未找到,沿 __proto__ 向上查找原型对象。
  3. 持续到 Object.prototype,若仍未找到返回 undefined
3. 代码示例
function Animal(type) {
  this.type = type;
}
Animal.prototype.eat = function() { 
  console.log("Eating...");
};

const myDog = new Animal("dog");
myDog.name = "Buddy";

// 原型链:myDog → Animal.prototype → Object.prototype → null
console.log(myDog.eat());    // 方法来自 Animal.prototype
console.log(myDog.toString()); // 方法来自 Object.prototype

三、原型链的创建与继承

1. 原型产生时机

原型在对象创建时即被确定,具体规则如下:

创建对象的方式 原型的指向 示例
对象字面量 默认指向 Object.prototype const obj = {};obj.__proto__ === Object.prototype
构造函数 (new) 实例的 __proto__ 指向构造函数的 prototype new Person()person.__proto__ === Person.prototype
Object.create() 显式指定原型对象 const child = Object.create(parent);child.__proto__ === parent
类 (class) 类语法是语法糖,实例原型指向类的 prototype(底层仍为原型继承) class Dog {}; new Dog()dog.__proto__ === Dog.prototype
2. 手动构建原型链
const animal = { eats: true };
const dog = Object.create(animal); // dog.__proto__ → animal
dog.name = "Buddy";

console.log(dog.eats); // true(通过原型链查找)

四、原型链污染漏洞与防御

1. 漏洞原理
  • 攻击方式通过篡改原型对象(如 Object.prototype),影响所有继承该原型的对象。
  • 触发条件:动态合并未过滤的敏感键名(如 __proto__)。
2. 攻击示例
// 不安全的深度合并函数
function unsafeMerge(target, source) {
  for (const key in source) {
    if (typeof source[key] === 'object' && source[key] !== null) {
      unsafeMerge(target[key], source[key]);
    } else {
      target[key] = source[key]; // 未过滤 __proto__
    }
  }
}

// 攻击者构造的恶意数据
const payload = JSON.parse('{"__proto__": {"isAdmin": true}}');
unsafeMerge({}, payload); 

// 验证污染
console.log({}.isAdmin); // true(所有对象被污染)
3. 防御措施
方法 实现方式 代码示例
过滤敏感键名 禁止操作 __proto__constructorprototype if (key === '__proto__') throw Error('Forbidden key');
使用无原型对象 Object.create(null) 创建的对象无原型链 const safeObj = Object.create(null);
冻结内置原型 禁止修改 Object.prototype Object.freeze(Object.prototype);
优先使用 Map Map 不依赖原型链,避免污染 const map = new Map(); map.set('isAdmin', true);

五、与 PHP 类继承的对比

特性 JavaScript 原型链 PHP 类继承
继承机制 对象继承对象(原型链) 类继承类(经典继承)
动态性 可随时修改原型和对象属性 类结构静态,属性需预先声明
方法共享 原型上的方法被所有实例共享 每个实例独立存储方法(除非 static
私有字段 通过 # 符号支持(ES2022+) 通过 private 关键字支持

六、关键方法与工具

1. 核心方法
方法/属性 作用 示例
Object.getPrototypeOf() 获取对象的原型 Object.getPrototypeOf(dog) === animal
Object.setPrototypeOf() 修改对象的原型(慎用) Object.setPrototypeOf(dog, newProto);
obj.hasOwnProperty() 检查属性是否属于对象自身(不查原型链) dog.hasOwnProperty('name')
in 操作符 检查属性是否存在(包括原型链) 'toString' in dog
2. 安全开发工具
工具 用途 链接
ppmap 自动化原型链污染扫描 [GitHub - ppmap]
CodeQL 静态代码分析 [GitHub - CodeQL]

七、常见问题与练习

1. 如何判断属性来源?
console.log(myDog.hasOwnProperty("name")); // true(自身属性)
console.log("toString" in myDog);          // true(来自原型链)
2. 练习:分析代码输出
function A() {}
A.prototype.x = 1;
const a = new A();
A.prototype = { x: 2 }; // 修改原型
const b = new A();
console.log(a.x, b.x); // 输出:1, 2(a 的原型未被动态修改)

八、总结

  • 原型链本质:通过 __proto__ 实现的链式继承机制,实现属性和方法共享。
  • 安全核心:永远不要信任用户输入的键名,严格过滤敏感属性。
  • 最佳实践:优先使用 MapObject.create(null) 等安全结构,避免直接操作原型。

网站公告

今日签到

点亮在社区的每一天
去签到