JavaScript原型链污染
本文最后更新于 16 天前,其中的信息可能已经有所发展或是发生改变。

先了解一下原型的定义

在javascript中,原型是一个对象,其他的对象可以通过原型实现属性和方法的继承,每一个js对象(除了null)都有一个内置的[[Prototype]]属性(在大多数浏览器中可以通过__proto__访问),这个属性指向他的原型对象

这里顺便也介绍一下,javascript对于对象自身的查找机制:

首先,会在对象自身查找该属性
如果找不到,则在其原型对象上查找
继续沿着原型链向上查找,知道找到该属性或者到达原型链末端(null)

简单举个例子:

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

Person.prototype.sayHello = function() {
  console.log(`Hello, I'm ${this.name}`);
};

const john = new Person('John');

// 原型链:
// john -> Person.prototype -> Object.prototype -> null

prototype&&__proto__

__proto__属性

首先,其是一个非标准的属性,他提供了对对象内部[[Prototype]]属性的访问,即我们可以直接获取或设置一个对象的原型对象

prototype

prototype是一个属性,每个对象都有,但是确切来说,所有的对象都有一个原型(prototype),这是一个内置的属性,它指向该对象的构造函数的原型对象。每当你创建一个新对象时,这个对象都会继承其构造函数的原型对象中的属性和方法

注:只有构造函数(即函数类型的对象)才有 prototype 属性

js是一种基于原型的编程语言,其没有传统的类继承机制,但是可以通过prototype实现类似的继承效果,允许对象继承其他对象的属性和方法

在js当中我们要定义一个类时,就需要定义构造函数的形式去定义,先上一个最简单的

function Foo(){
    this.bar = 1
}
new Foo()

其中this.bar就是Foo类中的一个属性,我们也可以定义方法在其中

function Foo() {
    // 给新创建的实例添加一个属性 bar 并赋值为 1
    this.bar = 1;
    // 给新创建的实例添加一个方法 show,用于打印 bar 属性的值
    this.show = function () {
        console.log(this.bar);
    };
}

// 创建 Foo 的一个实例并调用 show 方法
(new Foo()).show();

控制台打印出来是1,其实这样定义这个方法是绑定到对象身上的,而不是在原型内部的

function Foo(){
    this.bar = 1
}
Foo.prototype.show = function show() {
    console.log(this.bar)
}
let foo = new Foo()
foo.show()

可以使用prototype理解为能向对象中添加属性和方法,相当于实例化后的Foo类都能包含所有的属性和方法,不用一个个去定义了

foo.__proto__ = Foo.prototype
//一个对象的__proto__属性,指向这个对象所在类的prototype属性

所以就可以使用__proto__去访问Foo的原型,也就做到了继承

原型链污染

function Father(){
    this.first_name = "Donald"
    this.last_name = "Trump"

}
function Son(){
    this.first_name = "Melania"

}

Son.prototype = new Father()

let son = new Son()
console.log(`Name: ${son.first_name} ${son.last_name}`)

这样就做到了继承了,Son继承了father的所有属性和方法,然后根据js的寻找机制,若找不到就去 Son.__proto__中找,找不到就会一级一级的继承去找,这边我们将它和 father 做了继承,所以会找到 last_name 并输出,但是要注意的是,first_name 会输出 son 里面的,因为构造函数的调用顺序,在创建 Son 实例时,会覆盖掉从 Father 继承来的同名的属性

还有一个特点,就是,对于原型链继承,会让所有的Son实例共享Father实例的属性,如果修改了某个Son实例的原型属性,也会影响到其他的Son实例

举个例子:

function Father() {
    this.first_name = "Donald";
    this.last_name = "Trump";
}

function Son() {
    this.first_name = "Melania";
}

// 设置 Son 的原型为 Father 的一个实例
Son.prototype = new Father();

// 创建两个 Son 实例
let son1 = new Son();
let son2 = new Son();

// 修改 son1 的原型属性(这里以修改 last_name 为例)
son1.__proto__.last_name = "NewLastname";

// 查看 son2 的 last_name 属性
console.log(son2.last_name); // 输出: NewLastname

修改了Son1的原型属性,就会影响Son2的属性

所以,原型链污染就是利用的这个特点,通过修改原型属性,就会影响其他的类

关键也是要知道js的查找机制

同时对于`__proto__`和`prototype`的区别

`prototype` 属性是函数所独有的,而 `__proto__` 属性是每个对象都有的。

`prototype` 属性指向一个对象,它是用来存储属性和方法,这些属性和方法可以被该函数的实例对象所继承。而 `__proto__` 属性指向该对象的原型,它是用来实现对象之间的继承

dome

// 定义一个构造函数
  function Person(name, age) {
    this.name = name;
    this.age = age;
  }

  // 在 Person 的原型上定义一个方法
  Person.prototype.sayHello = function() {
    console.log('Hello, my name is ' + this.name + '.');
 }

  // 创建一个 Person 实例对象
  var person = new Person('Alice', 20);

  // 输出实例对象的属性和方法
  console.log(person.name);  // "Alice"
  console.log(person.age);   // 20
  person.sayHello();         // "Hello, my name is Alice."

  // 输出 Person 和实例对象的 __proto__ 属性
  console.log(Person.__proto__);     // [Function]
  console.log(person.__proto__);    // Person { sayHello: [Function] }

  // 输出 Person 和实例对象的 prototype 属性
  console.log(Person.prototype);    // Person { sayHello: [Function] }
  console.log(person.prototype);   // undefined  实例对象没有prototype属性,只有构造函数有,所以返回undefined

那么什么情况下才会存在污染呢?

污染中常见的危险函数

merge()

在javascript中,merge()并不是一个内置函数,当时通常指用于合并对象或数组操作
比如:
function merge(...objects) {
  return Object.assign({}, ...objects);
}

// 使用示例
const obj1 = { a: 1, b: 2 };
const obj2 = { b: 3, c: 4 };
const merged = merge(obj1, obj2); // { a: 1, b: 3, c: 4 }

clone()

在 JavaScript 中,clone() 不是原生提供的函数,但通常指创建一个对象的独立副本(克隆)的操作。由于 JavaScript 中对象和数组是通过引用传递的,简单的赋值不会创建真正的副本,因此需要专门的克隆方法
对于对象,比如:
// 方法1:使用 Object.assign()
function clone(obj) {
  return Object.assign({}, obj);
}

// 方法2:使用展开运算符
function clone(obj) {
  return { ...obj };
}

写一个dome

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]
        }
    }
}
let o1 = {}
let o2 = {a: 1, "__proto__": {b:2}}
merge(o1,o2)
console.log(o1.a,o2.b)
o3 = {}
console.log(o3.b)

merge 该函数用于递归合并两个对象。如果 sourcetarget 有相同属性名且对应值都是非 null 对象,则递归合并这两个子对象;否则直接将 source 的属性值赋给 target 对应属性

o1 是一个空对象。

o2 是一个具有属性 a 值为 1 的对象,同时通过 __proto__ 显式设置其原型对象,原型对象包含属性 b,值为 2

merge 函数开始遍历 o2 的可枚举属性

o2 有两个可枚举属性:a__proto__

对于属性 ao1 中没有该属性,所以 o1.a 会被赋值为 1

对于属性 __proto__,它也是一个可枚举属性(虽然 __proto__ 用于设置原型,但在这里作为普通属性被遍历),o1 中没有 __proto__ 属性,所以 o1.__proto__ 会被赋值为 {b: 2},也就是 o1 的原型被修改为包含属性 b 的对象

o1.a:由于 merge 操作将 o2.a 的值 1 赋给了 o1.a,所以 o1.a 输出为 1

o2.bbo2 原型对象上的属性,通过原型链查找可以访问到,所以 o2.b 输出为 2

o3 是一个新创建的空对象,它没有经过任何原型修改操作,其原型是默认的 Object.prototype,不包含属性 b

所以 o3.b 输出为 undefined

合并了但是并没有污染

因为,我们创建的o2的过程中,__proto__代表的o2的原型,此时遍历o2的所有的键名,拿到的事[a,b],__proto__并不是key,所以没有修改object的原型

function merge(target, source) {
    for (let key in source) {
        if (key in source && key in target) {
            if (typeof source[key] === 'object' && source[key]!== null && typeof target[key] === 'object' && target[key]!== null) {
                merge(target[key], source[key]);
            } else {
                target[key] = source[key];
            }
        } else {
            target[key] = source[key];
        }
    }
}

let o1 = {};
let o2 = { a: 1 };
// 直接修改 o2 的原型
Object.setPrototypeOf(o2, { b: 2 });
merge(o1, o2);
console.log(o1.a, o1.b); 

let o3 = {};
console.log(o3.b); 

o3 这样也就有了 b 属性,说明 Object 已经被污染

所以是直接影响到了 object,所以 o3 才会有 b2 的属性

总结一下可能存在的场景

不安全的对象合并/复制

function unsafeMerge(target, source) {
    for (let key in source) {
        target[key] = source[key]; // 可能覆盖 __proto__ 或 constructor
    }
}

const payload = { "__proto__": { isAdmin: true } };
unsafeMerge({}, payload);
console.log({}.isAdmin); // true(污染成功)

存在可以直接修改__proto__constructor.prototype

使用有漏洞的第三方库(间接导致污染)

Lodash (CVE-2019-10744):_.merge、_.defaultsDeep 曾存在漏洞

jQuery (CVE-2019-11358):$.extend(true, {}, payload) 可能被利用

Node.js 库(如 hoek、handlebars 等)

还有可能就是动态属性名赋值,比如可能赋值__proto__

暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇