JavaScript Prototype 污染攻击之 基础篇
JS创建类的方法
通过”字面量“方式创建
1 | var person = (name:'dongjc', work:function() {console.log('write coding')}); |
通过”构造函数“方式创建。
通过object方式创建
1 | var person = new Object(); |
JS 构造函数
JavaScript中,我们如果要定义一个类,需要以定义“构造函数”的方式来定义,它本身不提供一个 class 实现:
1 | function Foo(){ |
this.var
是Foo
类的一个属性
构造函数里定义方法
1 | function Foo(){ |
这样写就有一个问题,就是每当我们新建一个Foo对象时,this.show = function...
就会执行一次,这个show方法实际上是绑定在对象上的,而不是绑定在“类”中。
我希望在创建类的时候只创建一次show
方法,这时候就则需要使用原型(prototype
)了
关于原型Prototype
1 | 每个实例对象( object )都有一个私有属性(称之为 __proto__ )指向它的构造函数的原型对象( prototype )。该原型对象也有一个自己的原型对象( __proto__ ) ,层层向上直到一个对象的原型对象为 null。根据定义,null 没有原型,并作为这个原型链中的最后一个环节。 |
语法
1 | object.prototype.name = value |
有
1 | peo.__proto__ == Hu3sky.prototype |
或者
1 | peo.["__proto__"] |
于是 一个对象的__proto__
等于一个对象所在的类的prototype
基于原型链的继承
一段引用
1 | JavaScript 对象是动态的属性“包”(指其自己的属性)。JavaScript 对象有一个指向一个原型对象的链。当试图访问一个对象的属性时,它不仅仅在该对象上搜寻,还会搜寻该对象的原型,以及该对象的原型的原型,依次层层向上搜索,直到找到一个名字匹配的属性或到达原型链的末尾。 |
先看如下代码的运行结果
1 | function Father(){ |
Son
类继承了Father
类的last_name
调用规则:比如这个例子中
调son.last_name时:
- 在son对象中寻找last_name
- 找不到,到
son.__proto__
寻找last_name - 再找不到,到
son__proto__.__proto__
找 - 直到一个对象的原型对象为 null,比如,Object.prototype的proto就是null
总结
- 每个构造函数都有一个原型对象
- 对象的proto属性,指向类的原型对象prototype
- JavaScript使用prototype链实现继承机制
原型链污染
1 | //foo是一个简单的JavaScript对象 |
或者
原因也显而易见:因为前面我们修改了foo的原型foo.__proto__.bar = 2
,而foo是一个Object类的实例,所以实际上是修改了Object这个类,给这个类增加了一个属性bar,值为2。后来,我们又用Object类创建了一个xdeng对象let x = {}
,x对象自然也有一个bar属性了。那么,在一个应用中,如果攻击者控制并修改了一个对象的原型,那么将可以影响所有和这个对象来自同一个类、父祖类的对象。这种攻击方式就是原型链污染。
其实找找能够控制数组(对象)的“键名”的操作即可:
常见的对象有merge,clone
Example
merge
合并
1 | function merge(target,source){ |
于是可控点在key
,我们如果让key = __proto__
,那么不就能造成污染了
显然 __proto__
没被当成key,而是把后面的b当成了key,所以未能造成污染
那怎么把__proto__
当成key呢
修改o2
1 | var o2 = JSON.parse('{"a":"1","__proto__":{"b":"2"}}') |
成功污染,JSON解析的情况下,__proto__
会被认为是一个真正的“键名”,而不代表“原型”,所以在遍历o2的时候会存在这个键