JS原型链污染

JS原型链

JavaScript对象模型与原型链的底层结构

在现代js中,对象并非简单的键值对集合,而是基于原型链的继承体系

每个对象都拥有一个内部属性[[Prototype]],该属性指向其”原型对象”(即父级对象),从而形成一条可追溯的链式结构

__proto__和object.getprototypeof()的关系

__proto__是非标准的属性,但在实际开发和安全研究中被广泛使用,等价于Object.getpeototypeof()方法返回的结果

1
2
3
const arr=[];
console.log(arr.__proto__=== Array.prototype);//true
console.log(Object.getPrototypeOf(arr)===Array.prototype);//true
  • __proto__是 实例化对象的内部属性,用于访问其原型链的上一级
  • Object.getPrototypeOf(obj)是官方推荐的标准方法,用于获取任意对象的原型
  • 两者功能相同,但__proto__可能被某些框架或沙箱环境拦截或禁用,因此建议优先使用getPrototypeOf进行程序化分析

原型链的遍历顺序与终止条件

当访问对象的某个属性时,引擎会按照以下顺序查找

自身属性(own property)–>原型链上的属性–>直至null终止

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
function Animal() {}
Animal.prototype.eat = function() {
return "eating...";
};

const dog = new Animal();
dog.bark = function() {
return "woof!";
};

console.log(dog.eat()); // "eating..." —— 来自原型链
console.log(dog.bark()); // "woof!" —— 来自自身属性
console.log(dog.toString()); // "[object Object]" —— 来自 Object.prototype
console.log(dog.__proto__.__proto__); // null

//#运行结果
eating...
woof!
[object Object]
[Object: null prototype] {}

function Animal()

  • 定义一个构造函数Animal
  • 所用通过new Animal()创建的实例都会共享Animal.protype上的方法

Animal.prototype.eat = function()

  • 在Animal的原型对象上添加eat方法
  • 所有Animal实例(如dog)都能通过原型链访问它

const dog = new Animal();

  • 创建Animal的一个实例dog
  • dog.__proto__===Animal.prototype–>true

dog.bark = function()

  • 直接给dog实例添加一个自有属性bark
  • 这个方法是dog实例有的不会被其他Animal实例共享

console.log(dog.eat());

  • dog实例自身没有eat,于是沿原型链找
  • dog–>Animal.prototype(找到eat)–>执行
  • 输出eating…

console.log(dog.bark());

  • dog没有toString,继续沿原型链查找
  • dog–>Animal.prototype(无toString)–>Object.prototype(找到toString
  • Object.prototype.toString() 默认返回 [object Object]
  • [object Object]

``console.log(dog.proto.proto);`

  • dog.__proto__是Animal.prototype
  • Animal.prototype.__proto__Object.prototype
  • Object.prototype.__proto__是null(原型链的终点)
  • 所以:dog.__proto__.__proto__===Object.prototype–>true
  • dog.__proto__.__proto__.__proto__===null–>true

说明所有 对象最终都继承自Object.prototype,它是整个原型链根节点

内置原型对象

原型对象 作用
Object.prototype 所有对象的顶层原型,定义了如toStringhasOwnPropertuvalueOf等通用方法
Array.prototype 提供数组操作方法:push,pop,map,filter,reduce等
Function.prototype 函数的原型,包含call,apply,bind等方法
String.prototype 字符串方法如split,replace,includes等
Number.prototype 数值方法如toFixedtoExponential

这些原型对象一旦被污染,将影响全局行为。比如修改Object.prototype.toString可以导致所有对象的字符串表示异常甚至泄敏感信息

Array.prototype被篡改

1
2
3
4
5
6
7
8
// 污染 Array.prototype
Array.prototype.filter = function(){
return [1, 2, 3]; // 返回固定值,绕过逻辑判断
};

const nums = [10, 20, 30];
console.log(nums.filter(x => x > 15)); // [1, 2, 3] —— 完全失真复制

这种污染可能破坏数据校验、权限控制、日志记录等关键流程

__proto__的特殊性与安全性隐患

__proto__是一个可枚举属性,这意味着它可以出现在for……in循环中,并且可以被直接赋值

1
2
3
4
5
const obj = {};
obj.__proto__ = { isAdmin: true };

console.log({}.isAdmin); // true —— 污染生效!复制

核心漏洞成因

由于__proto__是所有对象共有的内置属性,攻击者可通过构造含有__proto__字段的对象输入,使合并函数将其误认为普通属性进行递归处理,从而污染Object.prototype

原型继承的实现方式与典型模式

构造函数与原型模式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// 定义构造函数
function Person(name, age) {
this.name = name;
this.age = age;
}

// 将方法添加到原型上
Person.prototype.greet = function() {
return `Hello, I'm ${this.name}, ${this.age} years old.`;
};

Person.prototype.isAdult = function() {
return this.age >= 18;
};

// 创建实例
const alice = new Person("Alice", 25);
const bob = new Person("Bob", 16);

console.log(alice.greet()); // Hello, I'm Alice, 25 years old.
console.log(bob.isAdult()); // false复制

定义构造函数

1
2
3
4
function Person(name, age) {
this.name = name;
this.age = age;
}

这是一个构造函数,用于创造具有相同结构的“Person”对象

  • Person是一个普通函数,但约定首字母大写表示它是构造函数

  • 使用new Person()调用时

    • 会创建一个新对象{}
    • 将这个对象的__proto__指向Person.prototype
    • 执行函数体,其中this指向这个新对象
    • 如果汉纳树没有显示返回对象,则自动返回这个新对象
  • this.name = name;将传入的参数name赋值给新对象name属性,这是实例自身的属性,不与其他共享

向原型添加方法

1
2
3
4
5
6
7
Person.prototype.greet = function() {
return `Hello, I'm ${this.name}, ${this.age} years old.`;
};

Person.prototype.isAdult = function() {
return this.age >= 18;
};

将greet和isAdult两个方法添加到Person的原型对象上,供所有实例共享使用

创建实例并调用方法

1
2
const alice = new Person("Alice", 25);
const bob = new Person("Bob", 16);
步骤 alice 实例 bob 实例
创建 new Person("Alice", 25) new Person("Bob", 16)
自身属性 name: "Alice", age: 25 name: "Bob", age: 16
原型链接 alice.__proto__ === Person.prototype bob.__proto__ === Person.prototype

调用方法并输出

1
2
console.log(alice.greet());     // Hello, I'm Alice, 25 years old.
console.log(bob.isAdult()); // false复制

alice.greet()

  • JS引擎查找greet方法

    • 先看alice自身有没有greet–>这里自身没有
    • 沿原型链向上继续查找–>alice.__proto__(即Person.prototype)–>找到greet函数
  • 执行greet(),此时this=alice

  • 返回:Hello, I'm Alice, 25 years old.

bob.isAdult()

  • 查找isAdult

    • bob自身没有–>沿着原型链向上查找Person.prototype找到
  • 执行时this=bob,所以this.age=16

  • 判断16>=18–>false

原型链图解

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
alice
├── name: "Alice"
├── age: 25
└── __proto__ → Person.prototype
├── greet: function()
├── isAdult: function()
└── __proto__ → Object.prototype
├── toString: function()
├── hasOwnProperty: function()
└── __proto__ → null

bob
├── name: "Bob"
├── age: 16
└── __proto__ → Person.prototype (与 alice 共享!)

object.create()显式创建原型链

object.create()允许你显式指定对象的类型,是构建原型链的高级手段

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 以 Object.prototype 为原型创建新对象
const obj1 = Object.create(null);
console.log(obj1.__proto__); // null —— 无原型链

// 以某对象为原型
const parent = { color: 'red' };
const child = Object.create(parent);

console.log(child.color); // red
child.color = 'blue';
console.log(child.color); // blue —— 自身属性覆盖原型属性

// 修改原型
parent.color = 'green';
console.log(child.color); // blue —— 未受影响(因为已设置自身属性)复制

若传入恶意对象作为原型,可能导致原型污染

1
2
3
const maliciousProto={'__proto__':{'isAdmin':true}};
const evilobj=Object.create(maliciousProto);
console.log({}.isAdmin);//true——污染生效复制

ES6 类语法背后的原型机制

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class Car {
constructor(brand, model) {
this.brand = brand;
this.model = model;
}

start() {
return `${this.brand} ${this.model} is starting...`;
}

static info() {
return "This is a car class.";
}
}

const tesla = new Car("Tesla", "Model S");

console.log(tesla.start()); // Tesla Model S is starting...
console.log(Car.info()); // This is a car class.
console.log(tesla.__proto__ === Car.prototype); // true
console.log(Car.prototype.__proto__ === Object.prototype); // true复制

class Car

定义一个Car类

constructor(brand, model)

构造函数,用于初始换属性brand和model

start()

实例方法,绑定在Car.prototype上,所有实例共享

static info()

静态方法,只属于Car.info本身,不能共享给其他类

const tesla = new Car("Tesla", "Model S");

将Car实例化成一个新的对象,内部属性为{ brand: “Tesla”, model: “Model S” }

console.log(tesla.start());

在tesla中调用公有实例方法start,访问了this.brand和this.model

等价转换

1
2
3
4
5
6
7
8
9
10
11
// class Car 等价于:
function Car(brand, model) {
this.brand = brand;
this.model = model;
}
Car.prototype.start = function() {
return `${this.brand} ${this.model} is starting...`;
};
Car.info = function() {
return "This is a car class.";
};复制

显式原型赋值于隐式原型链查找的差异

行为 描述 示例
显示原型赋值 直接修改constructor.prototype Person.prototype.sayHi=()=>{}
隐式原型链查找 通过 __proto__ obj.someMethod()会沿着原型链查找
1
2
3
4
5
6
7
8
9
10
11
function Dog() {}
Dog.prototype.bark = function() { return "Woof!"; };

const myDog = new Dog();

// 隐式查找
console.log(myDog.bark()); // Woof!

// 显式赋值(影响所有实例)
Dog.prototype.bark = function() { return "Bark!"; };
console.log(myDog.bark()); // Bark!复制

console.log(myDog.bark());

调用myDog.bark()时,JS首先在myDog对象自身上查找bark方法

由于myDog不存在bark方法,会通过原型链在Dog.prototype上找到该方法

Dog.prototype.bark = function() { return "Bark!"; };

直接修改Dog.prototype.bark时所有通过new Dog()创建的实例都会受到影响

⚠️JS的原型链继承是动态的

⚠️修改原型对象会影响所有已创建的和未创建的实例

原型链污染的触发前提总结


JS原型链污染
https://colourful228.github.io/2026/02/11/JS原型链污染/
作者
Colourful
发布于
2026年2月11日
更新于
2026年2月20日
许可协议