Hu3sky's blog

JavaScript Prototype 污染攻击之 基础篇

Word count: 1,128 / Reading time: 5 min
2019/05/06 Share

JavaScript Prototype 污染攻击之 基础篇

JS创建类的方法

通过”字面量“方式创建

1
var person = (name:'dongjc', work:function() {console.log('write coding')});

通过”构造函数“方式创建。

通过object方式创建

1
2
3
4
5
6
7
var person = new Object();
person.name = "dongjc";
person.age = 32;
person.Introduce = function () {
alert("My name is " + this.name + ".I'm " + this.age);
};
person.Introduce();

JS 构造函数

JavaScript中,我们如果要定义一个类,需要以定义“构造函数”的方式来定义,它本身不提供一个 class 实现:

1
2
3
4
function Foo(){
this.var = "hu3sky"
}
new Foo()

this.varFoo类的一个属性

1.png

构造函数里定义方法

1
2
3
4
5
6
7
function Foo(){
this.bar = 1
this.show = function(){
console.log("This is func")
}
}
(new Foo()).show()

1.png

这样写就有一个问题,就是每当我们新建一个Foo对象时,this.show = function...就会执行一次,这个show方法实际上是绑定在对象上的,而不是绑定在“类”中。

我希望在创建类的时候只创建一次show方法,这时候就则需要使用原型(prototype)了

关于原型Prototype

1
2
每个实例对象( object )都有一个私有属性(称之为 __proto__ )指向它的构造函数的原型对象( prototype )。该原型对象也有一个自己的原型对象( __proto__ ) ,层层向上直到一个对象的原型对象为 null。根据定义,null 没有原型,并作为这个原型链中的最后一个环节。
prototype 属性允许您向对象添加属性和方法

语法

1
object.prototype.name = value

1.png

1
peo.__proto__ == Hu3sky.prototype

或者

1
peo.["__proto__"]

于是 一个对象的__proto__等于一个对象所在的类的prototype

基于原型链的继承

一段引用

1
JavaScript 对象是动态的属性“包”(指其自己的属性)。JavaScript 对象有一个指向一个原型对象的链。当试图访问一个对象的属性时,它不仅仅在该对象上搜寻,还会搜寻该对象的原型,以及该对象的原型的原型,依次层层向上搜索,直到找到一个名字匹配的属性或到达原型链的末尾。

先看如下代码的运行结果

1
2
3
4
5
6
7
8
9
10
function Father(){
this.first_name = 'Hu3sky'
this.last_name = 'a'
}
function Son(){
this.first_name = 'LDH'
}
Son.prototype = new Father()
let son = new Son()
console.log(`Name: ${son.first_name} ${son.last_name}`)

1.png

Son类继承了Father类的last_name
调用规则:比如这个例子中
调son.last_name时:

  1. 在son对象中寻找last_name
  2. 找不到,到son.__proto__寻找last_name
  3. 再找不到,到son__proto__.__proto__
  4. 直到一个对象的原型对象为 null,比如,Object.prototype的proto就是null

总结

  1. 每个构造函数都有一个原型对象
  2. 对象的proto属性,指向类的原型对象prototype
  3. JavaScript使用prototype链实现继承机制

原型链污染

1
2
3
4
5
6
7
//foo是一个简单的JavaScript对象
let foo = {bar:1}
//修改foo的原型
foo.__proto__.bar = 2
// 由于查找顺序的原因,foo.bar仍然是1
// 此时再用Object创建一个空的zoo对象
let x = {}

1.png

或者
1.png

原因也显而易见:因为前面我们修改了foo的原型foo.__proto__.bar = 2,而foo是一个Object类的实例,所以实际上是修改了Object这个类,给这个类增加了一个属性bar,值为2。后来,我们又用Object类创建了一个xdeng对象let x = {},x对象自然也有一个bar属性了。那么,在一个应用中,如果攻击者控制并修改了一个对象的原型,那么将可以影响所有和这个对象来自同一个类、父祖类的对象。这种攻击方式就是原型链污染。

其实找找能够控制数组(对象)的“键名”的操作即可:
常见的对象有merge,clone

Example

merge合并

1
2
3
4
5
6
7
8
9
10
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.png

于是可控点在key,我们如果让key = __proto__,那么不就能造成污染了
1.png
1.png

显然 __proto__没被当成key,而是把后面的b当成了key,所以未能造成污染
那怎么把__proto__当成key呢
修改o2

1
var o2 = JSON.parse('{"a":"1","__proto__":{"b":"2"}}')

1.png
成功污染,JSON解析的情况下,__proto__会被认为是一个真正的“键名”,而不代表“原型”,所以在遍历o2的时候会存在这个键

Referers

CATALOG
  1. 1. JavaScript Prototype 污染攻击之 基础篇
    1. 1.1. JS创建类的方法
      1. 1.1.1. 通过”字面量“方式创建
      2. 1.1.2. 通过”构造函数“方式创建。
      3. 1.1.3. 通过object方式创建
    2. 1.2. JS 构造函数
    3. 1.3. 关于原型Prototype
    4. 1.4. 基于原型链的继承
    5. 1.5. 原型链污染
      1. 1.5.1. Example
    6. 1.6. Referers