沉铝汤的破站

IS LIFE ALWAYS THIS HARD, OR IS IT JUST WHEN YOU'RE A KID

JS原型链污染攻击

0x00 前言


与之前所学过的基于类的面向对象语言不同的,JavaScript是一门基于原型的的语言——每个对象拥有一个原型对象。这表示JS中并不像之前那些面向对象语言一样,先创建类再创建一个对象(虽然之后引入了”class“,是只是语法糖,实际还是基于原型),在JS中,有“一切皆是对象”的说法。那什么是“原型“,JS中又是如何不同寻常地实现面向对象的呢?

管你听没听懂

0x01 原型


初见原型

在JS中,一切皆是对象,所以函数也是对象,它也可以有自己的属性。而每个构造函数都有一个特殊的属性:prototype,即原型,而“原型”也是一个对象,所以原型也可以有自己的属性。我们可以使用如下的代码在浏览器控制台中打印出一个函数的原型:

function foobar(){
    this.bar="bar";
    this.hello = function(){
        console.log("HELLO");
    }
};
console.log(foobar.prototype);

在原型对象中,有两个属性。construtor指向了构造函数本身,__proto__将在下文解释。

image-20210304164324808

实例化一个对象

上文我们查看了构造函数的原型对象,但我们还不知道它的具体作用,先回到本文的问题,“JS是怎么实现面向对象的呢”,也即“JS是如何实现继承等面向对象特性的呢?”。

使用下面代码实例化一个对象(JS中的对象是通过构造函数实现),并在控制台中输出。

function foobar(){
    this.bar="bar";
    this.hello = function(){
        console.log("HELLO");
    }
};
var myfoo = new foobar();
console.log(myfoo);

可以看到,myfoo实例化对象中的__proto__居然和构造函数的原型对象一样,这有什么用呢?这是不是与属性和方法的继承有关呢?我们可以做个实验:先实例化一个对象,再给构造函数的原型添加一个属性,然后再调用这个属性看看

image-20210304164735899

实验一下

实验代码如下:

function foobar(){
    this.bar="bar";
    this.hello = function(){
        console.log("HELLO");
    }
};
var myfoo = new foobar();
foobar.prototype.test = "THIS IS TEST";
console.log(myfoo.test);

我们可以惊奇的发现,我们先实例化后,再给构造函数的原型添加一个属性,居然可以在实例化对象中调用。

image-20210304205152203

并且,当我们再次查看__proto__时,发现也它发生了变化,自动加上了我们为构造函数原型对象添加的属性。(也就是说始终和构造函数的原型对象同步变化)

image-20210304211004645

所以,实验结果表明,构造函数的原型对象确实是与实例化对象的属性和方法的继承有关。;并且实例对象的__proto__是指向构造函数的原型对象prototype的;另外,我们隐隐感觉到,实例对象调用原型中新加的属性(或许是所有属性/方法)似乎应该也是通过了__proto__(毕竟新属性在这里很显眼的出现了,并没有出现在和”bar”同层次)。

0x02 原型链


上文提到实例对象调用属性/方法,似乎与__proto__有着一丝关系,实际上,这两者确实是有关系,并且关系还不小——这便是原型链。

知识点来咯

我们以上文的例子进行分析,来理清原型的作用以及原型链的意义。当我们调用实例化对象myfoo中的属性(/方法,下文省略)时,JS会先在myfoo自己这先寻找这个属性,当不存在时,便会在__proto__中(即foobar的原型对象)寻找,若再没找到,则会在__proto____proto__中(即foobar的原型对象的__proto__)寻找。默认情况下,函数原型对象的__proto__指向的是object.prototype。此时如果还是没找到,因为object__proto__不存在,就会停止寻找,得出结论,这个属性是undefined

以上一层层的__proto__便构成了原型链

大白话的总结

可以看到,原型prototype似乎就类似于装备的蓝图,而__proto__就类似于一个点了就会跳转到对应蓝图的链接(这样说可能不太准确,因为实际上是myfoo.__proto__=foobar.prototype)。当我们想制作一件🐂🍺的装备时,居然发现材料缺少😡,然后我们就点到原材料上,居然发现原材料也要合成(🤦‍♂️),然后我们点进原材料的原材料,居然发现还是材料不足💢,于是要我们点进原材料的原材料的原材料的原材料的原材料(艹),最后显示首充即送100个。哈哈,看着火气就直接上来了🔥🔥🔥

0x03 原型链污染


上文我们在探究的实验过程中,好像发现一个事情,我们先实例化了一个对象,然后再给构造函数的原型对象加上一个属性,然后再用实例化对象去调用新属性,居然可以调用成功(好像Python也有动态添加属性,学艺不精,不确定)。我们是修改了构造函数达成了修改实例对象的效果,那我们是不是可以试想一下下面的这个实验,是否能通过实例对象来实现修改构造函数,从而影响之后创建的实例化对象。

新实验

  1. 先创建一个构造函数Father,然后实例化一个对象son1
  2. son1__proto__添加一个新属性
  3. 再利用Father实例化一个对象son2
  4. 通过son2调用新属性,观察是否调用成功

代码

function Father(){
    
}

let son1 = new Father();
console.log(son1.foo);
son1.__proto__. foo = 'foobar';
console.log(son1.foo);

let son2 = new Father();
console.log(son2.foo);

实验结果

可以看到,通过son1确实可以成功修改来自同一“祖先”的son2

image-20210304230013163

原型链污染

其实上面的实验便基本阐述了“原型链污染”的过程,通过修改一个对象“父类”的原型,来影响其他来自同一“祖先”的对象。

0x04 攻击实例


懒🐕 ,将在后面的文章,收集一些CTF题目进行实践。

0x05 参考文章


MDN

P神

从几个例子分析JavaScript Prototype 污染攻击

JS中for in 循环遍历数组会遍历原型链中属性问题

网鼎杯 notes