写在前面
已经遇见过好几道原型链污染的题目了,一直没有整体学习整理过.国赛又碰见了,然而没做出来,痛定思痛,准备学习一下.
原型与原型链
JavaScript是一门面向对象语言,有意思的一点是,它语法中并没有class的概念,但是它仍然大量使用了对象。为了保证对象之间的联系,JavaScript引入了原型和原型链的概念。
构造函数与原型
为了保证JavaScript“看起来像Java”,JavaScript中也加入了new操作符:
1 | var obj = new FunctionName() |
但与Java不同的是,JavaScript中的new操作符后面跟的并不是类名而是函数名,JavaScript并非通过类二十直接通过构造函数来创建实例。
1 | function Dog(name, color) { |
上述代码就是JavaScript中通过构造函数创建实例的过程,但这种方式有一个很大的问题。通过构造函数创建实例的时候,每创建一个实例,都需要重新创建这个方法,再把它添加新的实例中。
这样就造成了很大的浪费,所以需要一种方法来解决这个问题。原型(prototype)
的使用就是为了解决这个问题:
每一个构造函数都拥有一个prototype属性,这个属性指向一个对象,也就是原型对象。当使用这个构造函数创建实例的时候,prototype属性指向的原型对象就成为实例的原型对象。
原型对象默认拥有一个constructor属性,指向指向它的那个构造函数(也就是说构造函数和原型对象是互相指向的关系)。
每个对象都拥有一个隐藏的属性
prototype
,指向它的原型对象,这个属性可以通过 Object.getPrototypeOf(obj) 或 obj.proto 来访问。
例如上述代码如上文所述,原型对象就是用来存放实例中共有的那部分属性。
在JavaScript中,所有的对象都是由它的原型对象继承而来,反之,所有的对象都可以作为原型对象存在。
访问对象的属性时,JavaScript会首先在对象自身的属性内查找,若没有找到,则会跳转到该对象的原型对象中查找。
上述代码中各对象关系可表示为下图
通过原型,我们可以将上述代码修改为:
1 | function Dog(name, color) { |
实例化dog1和dog2,我们发现他们bark函数相同,也就解决了之前的问题.
原型链与继承
上文提到,JavaScript中所有的对象都是由它的原型对象继承而来。而原型对象自身也是一个对象,它也有自己的原型对象,这样层层上溯,就形成了一个类似链表的结构,这就是原型链(prototype chain)
。
所有原型链的终点都是Object函数的prototype属性,因为在JavaScript中的对象都默认由Object()构造。Objec.prototype指向的原型对象同样拥有原型,不过它的原型是null,而null则没有原型。
通过原型链就可以在JavaScript中实现继承,JavaScript中的继承相当灵活,有多种继承的实现方法,这里只介绍一种最常用的继承方法也就是组合继承。
1 | function Dog(name, color) { |
这里声明了一个新的构造函数Husky,通过call方法继承Dog中的属性(call方法的作用可以简单理解为将Dog中的属性添加到Husky中,因为还涉及到其他的知识点所以不多赘述),并添加了一个weight属性。然后用Dog函数创建了一个实例作为Husky的原型对象赋值给Husky.prototype以继承方法。这样,通过Husky函数创建的实例就拥有了Dog中的属性和方法。
原型链污染
为了更方便描述,我们修改一下之前的代码
1 | function Dog(name) { |
我们发现如果修改dog1.Proto.color = 1, dog2的color也会改变,这样的操作污染了原型链,称之为原型链污染.
也就是,在一个应用中,如果攻击者控制并修改了一个对象的原型,那么将可以影响所有和这个对象来自同一个类、父祖类的对象。这种攻击方式就是原型链污染。
实例
hackit 2018
1 | const express = require('express') |
查看源码可知,我们需要令md5(user.admintoken) === req.query.querytoken
且 user.admintoken存在
,而源码中显示user是个 []类
这个类并没有开发完成,所以我们的目标就是给user.admintoken赋值.
根据原型链的概念,当user.admintoken没有值时,会递归访问proto查询,所以我们可以尝试寻找某个[]类
并修改它的原型.
在/api
接口中,函数会接收传参,并matrix[client.row][client.col] = client.data;
.
而matrix[“proto“]就是matrix对象的原型,这样就造成了原型链污染.
payload:
xss13
源码如下
1 | function escape(input) { |
我们分析下源码
1 | try { |
主函数将用户传参进行JSON.parse(input)
替换成原有的图片资源,这边造成了原型链污染.
如果我们传入的source值不符合正则,他将删除config.source
我们尝试传入{"source":"*", "__proto__": {"source":"1"}}
,验证了我们之前的分析
接下来要做的就是xss注入的绕过"
了
查看js文档中replace函数性质:
字符串 stringObject 的 replace() 方法执行的是查找并替换的操作。它将在 stringObject 中查找与 regexp 相匹配的子字符串,然后用 replacement 来替换这些子串。如果 regexp 具有全局标志 g,那么 replace() 方法将替换所有匹配的子串。否则,它只替换第一个匹配子串。
replacement 可以是字符串,也可以是函数。如果它是字符串,那么每个匹配都将由字符串替换。但是 replacement 中的 $ 字符具有特定的含义。如下表所示,它说明从模式匹配得到的字符串将用于替换。
我们先来测试下这个特性
这样我们就可以通过'<img src="_posts/js原型污染.md">'.replace('_posts/js原型污染.md', source)
中_posts/js原型污染.md前的"
来闭合,进而注入
payload如下:
1 | {"source":"*", "__proto__": {"source":"$` onerror='prompt(1)' "}} |
Thejs
题目源码地址https://github.com/phith0n/code-breaking/blob/master/2018/thejs
题目部署地址http://106.15.177.94:10025/
看了很多wp,不太清楚清楚师傅们是怎么泄露到源码或者fuzz到注入点的,可能是赛程中放出了主要代码吧.
这里就直接分享p神的题解了深入理解 JavaScript Prototype 污染攻击
参考资料
三分钟看完JavaScript原型与原型链
JavaScript 原型链污染
深入理解 JavaScript Prototype 污染攻击
浅析javascript原型链污染攻击