原型链污染漏洞
前言
深入了解javascript
对象与类
JavaScript一切都是对象,我们先来了解对象
创建一个简单的js对象如下:
var obj ={};
创建obj这个对象师,并没有赋予它任何的属性和方法,但是他会具有一些内置属性和方法,像是__proto__,constructor,toString等。
为了探究这些内置属性是怎么来的,我们需要看一下JavaScript中类的一些机制,JavaScript中的类是从一个函数开始的;
函数对象:
function MyClass(){
console.log("qaq");
}
var inst = new MyClass();
//以上代码创建了一个MyClass函数,同时MyClass也是一个类,可以像别的语言中那样为这个类实例化一个对象inst
以上代码的执行结果,在实例化inst时候,MyClass()也同样执行了
这可以联想带构造函数,构造函数的特性就是在new一个对象的时候执行
所以MyClass()函数与MyClass这个类的关系是前者是后者的构造函数
通过constructor这个属性还可以查看对象的构造函数
__proto__与prototype
prototype是一个类的属性,所有类对象在实例化的时候将会拥有prototype中的属性和方法
类在运行运行时是可以修改的
function test (){
this.a = "test";}
undefined
b = new test;
test{a:"test"}
console.log(b.a);
test
可以看到b在实例化为test对象以后,就可以输出test类中的属性a了。这是为什么?
原因在于js中一个重要的概念:继承
而继承的整个过程就成为该类的原型链。
//在javascript中,每个对象的都有一个指向他的原型(prototype)的内部链接,这个原型对象又有它自己的原型,直到null为止
function i(){
this.a = "test1";
this.b = "test2";}
可以看到其父类为object,且里面还有许多函数,这就解释了为什么许多变量可以调用某些方法。
在javascript中一切皆对象,因为所有的变量,函数,数组,对象 都始于object的原型即object.prototype。同时,在js中只有类才有prototype属性,而对象却没有,对象有的是__proto__
和类的prototype
对应。且二者是等价的
>function Foo(){
this.bar =1;
}
<undefined
>Foo.prototype.show = function show ()
console.log(this.bar);}
<show (){
console.log(this.bar0:}
>let foo +new Foo
<undefinde
>Foo.prototype;
<{show:f,constructor:f}
>foo.__proto__
<{shwo:f,constructor;f}
>Foo.prototype -- foo.__proto__
<true
了解原型链
类
当我们创建一个类的时候,原型链为b ->a.prototype ->object.prototype ->null
数组
当我们创建一个数组的时候,原型链为c ->array.prototype ->object.prototype ->null
函数
当我们创建一个函数时,原型链为d ->function.prototype ->object.prototype ->null
日期
当我们创建一个日期时,原型链为f ->Data.prototype -> object.prototype ->null
原型链变量搜索
>function g (){
this.a ="test1";
this.b ="test2";}
<undefined
>g.prototype.c ="test3";
<"test3"
>function i(){
this.a ="test1";
this.b ="test2";}
<undefined
>var j = new(i);
<undefined
>i.prototype.c = "test3";
<"test3"
>console.log(j.c);
test3
在实例要先于在i中添加属性,但是在j中也有了c属性。
这是为什么?
当要使用或输出一个变量时:首先会在本层中搜索相应的变量,如果不存在的话,就会向上搜索,即在自己的父亲中搜索,当父类目录中也没有时,就会向祖父类搜索,知道指向null,如果此时还没有搜索到,就会返回undefined
小总结
1JavaScript是一个神奇的语言,一切皆对象
2对象都有一个__proto__属性,指向它的类的”prototype"
3类是通过函数来定义的,定义的这个函数又是这个类的constructor属性值
4每个构造函数constructor都有一个原型对象prototype
5JavaScript使用prototype链实现继承机制
6子类是可以通过prototype链修改其父类属性,以及爷爷类的属性值
原型链污染
小实验
// foo是一个简单的JavaScript对象
let foo = {bar: 1}
// foo.bar 此时为1
console.log(foo.bar)
// 修改foo的原型(即Object)
foo.__proto__.bar = 2
// 由于查找顺序的原因,foo.bar仍然是1
console.log(foo.bar)
// 此时再用Object创建一个空的zoo对象
let zoo = {}
// 查看zoo.bar
console.log(zoo.bar)
zoo.bar的结果是2;
因为前面修改了foo的原型foo.__proto__.bar = 2,而foo是一个Object类的实例,所以实际上是修改了Object这个类,给这个类增加了一个属性bar,值为2。
后来,又用Object类创建了一个zoo对象let zoo = {},zoo对象自然也有一个bar属性了。
那么,在一个应用中,如果攻击者控制并修改了一个对象的原型,那么将可以影响所有和这个对象来自同一个类、父祖类的对象。这种攻击方式就是原型链污染。
原型链会被污染的情况
第一种
obj[a][b] = value
obj[a][b][c] = value
如果控制了a,b,c及value就可以进行原型链污染的攻击
可以控制a=__proto__
第二种
merge (target, source)
foreach property of source
if property exists and is an object on both the target and the source
merge(target[property], source[property])
else
target[property] = source[property]
这种情况下,__proto__必须被视为key才能成功
对于
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]
}
}
}
//1.
let o1 = {}
let o2 = {a: 1, "__proto__": {b: 2}}
merge(o1, o2)
console.log(o1.a, o1.b)
o3 = {}
console.log(o3.b)//undefined
//2.
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)//2
1和2两种情况是不一样的.
因为前面代码中 __proto__
已经代表o2的原型了 ,没有被看成一个key
后面的代码中经过JSON.parse解析,__proto__
就代表了一个key
补充
merge
递归合并JavaScript对象中的值,深合并递归,将源的属性合并到目标中