原型链污染

原型链

什么是prototype和__proto__?

在JavaScript中,我们如果要定义一个类,就需要一定以"构造函数"的方式来定义

function Foo(){
    this.bar = 1
}

new Foo()


Foo函数的内容,就是Foo类的构造函数,而this.bar就是Foo类的一个属性

一个类必然有一些方法,类似属性this.bar,我们也可以把方法定义在构造函数内部

function Foo(){
    this.bar = function(){
        console.log(this.bar)
    }
}

(new Foo()).show()


但是这样就会在每次新建一个Foo对象的时候,this.show = function…就会被执行一次,这样子这个show方法就是和对象进行绑定,而不是和"类"进行绑定了

怎么避免重复,就需要使用原型(prototype)了

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


可以认为原型prototype是类Foo的一个属性,而所有Foo由类 实例化出来的对象,都将拥有这个属性中的所有内容,包括其中的变量和方法,以上代码中的foo,创建的时候就拥有了foo.show()方法

可以通过Foo.prototype来访问Foo类的原型,但是Foo实例化出来的对象,不能通过prototype来访问原型。这种情况下就要使用到__proto__了。

一个Foo类进行实例化出来的foo对象,可以通过foo.__proto__属性来访问Foo的原型

所以可以得到

foo.__proto__ == Foo.prototype

image-20210919195220277
prototype是一个类的属性,所有类对象在实例化的时候将会拥有prototype中的属性和方法

一个对象的__proto__属性,指向这个对象所在的prototype属性

简单理解就是prototype是用来创造对象用的,往前走

__proto__是用来回朔用的,往回找

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

function Son(){
    this.first_name = 'hhh'
}

Son.prototype = new Father()

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


Son类继承了Father类的last_name属性

对于对象son,在调用last_name的时候,JavaScript引擎会进行的操作如下:

在对象son中寻找last_name
如果找不到,就到son.__proto__中寻找last_name
还找不到,就到son.__proto__.__proto__中寻找last_name
就这样一直往上找,一直找到null宣告结束

image-20210919201638852

image-20210922184614279

注意:每个构造函数{constructor}都有一个原型对象{prototype}
           对象的__proto__属性,指向类的原型对象prototype
           JavaScript使用prototype来实现继承的机制


什么是原型链污染


现在我们知道了foo.__proto__指向的是Foo类的prototype。那么如果我们修改了foo.__proto__中的值,是不是就可以修改Foo类?

let foo = {bar:1}//先创建一个对象

console.log(foo.bar)//js先在foo对象中找bar,找到了,值为1

foo.__proto__.bar = 2//修改foo的原型,其实是修改了Object这个类,给这个类增加了bar=2

console.log(foo.bar)//js还是在foo对象中找bar,并不会往后的prototype找bar

let zoo = {}//创建一个空的zoo对象

console.log(zoo.bar)//因为zoo是一个空对象,所以第一次寻找找不到,只能往上找,找到了Object的bar

这样子,在一个应用中,如果攻击者控制并且修改了一个对象的原型,那么就可以影响所有和这个对象来自同一个类、父祖类的对象,这种攻击方式就叫做原型链污染。

哪些情况下原型链会被污染?


现在我们知道了控制对象的 __proto__ ,即可影响该实例的父类,那么要如何控制 __proto__ 呢?

JS中针对对象的复制分为浅拷贝和深拷贝,简单来说:

浅拷贝 只是将指向对象的指针复制了过去,不论如何拷贝,这些拷贝都指向同一个引用,一旦被修改,所有引用都会变化;

深拷贝 则是要将目标对象完完全全的“克隆”一份,占据自己的内存空间。

实现深拷贝,一种常见的方式是:递归遍历需要复制对象的所有属性,并且全部赋值给新的空对象,实际上创建了一个新的对象。而浅拷贝就是引用。

在实际应用中,哪些情况下可能存在原型链能被攻击者修改的情况呢?

这里做一个举例

对象merge
对象clone
以对象merge为例子,我们想象一个简单的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]
        }
    }
}


在合并的过程中,存在赋值的操作target[key] = source[key],那么,这个key如果是__proto__,是不是就可以原型链污染呢?

我们用如下代码实验一下:

let o1 = {}
let o2 = {a: 1, "__proto__": {b: 2}}
merge(o1, o2)
console.log(o1.a, o1.b)

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

//1 2
//undefined


虽然合并在了一起,但是并没一被污染。因为我们用JavaScript创建o2的过程(let o2 = {a: 1, “__proto__”: {b: 2}})当中,__proto__被认为是o2本对象的原型,此时又会遍历o2的所有键名,拿到的是a和b两个键,__proto__并不是一个key,自然也不会修改Object的原型(我们自己创建的对象都是以Object为原型创建来的)

let o1 = {}
let o2 = JSON.parse('{"a": 1, "__proto__": {"b": 2}}')
merge(o1, o2)
console.log(o1.a, o1.b)

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

//1 2
//2


此时利用JSON.parse方法,这个方法可以将JSON字符串解析为值或对象。所以在JSON解析的情况下,__proto__会被认为是一个真正的“键名”,而不代表“原型”,所以在遍历o2的时候会存在这个键。

再来个例子

上面那个是通过__proto__来实现漏洞,还有另一种方式:重载构造函数

当我们将constructor和prototype嵌套作为键名的时候

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 = JSON.parse('{"constructor": {"prototype": {"hello": 1}}}')
merge({},o1)

let o2 = {}
console.log(o2.hello)


实例 constructor 的 prototype ,和实例的__proto__指向一致。由于 merge 操作的解析是递归的,这种方式同样也会污染 Object

参考文献:复现原型链污染漏洞_Liu_Cheng_H的博客-CSDN博客​​​​​​

参考文献:网络安全--原型链污染_沐芊屿的博客-CSDN博客

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值