JavaScript 原型链污染
前言
在浏览某个论坛的时候,第一次看到了 JavaScript 原型链污染漏洞。当时非常的好奇,当时我一直以为 js 作为一种前端语言,就算存在漏洞也是针对前端,不会危害到后端,因此我以为这种漏洞危害应该不大。可当我看到他的漏洞危害还有可以执行任意命令的时候,发现可能我想到有点简单了。js 也是可以用来做后端语言的。这篇文章就来认识一下这个漏洞。
JavaScript 原型链是什么?
既然漏洞名称是 JavaScript 原型链污染,那么首先就要先明白 JavaScript 原型链是什么。
正如我们所知,Javascrip 的复杂类型都是对象类型(Object),而 js 不是一门完全面对对象编程的语言。那么对于对象编程来说要考虑对象的继承。
js 实现继承的核心就是原型链。我理解的就是原型链的存在就是 js 中的继承机制,保证函数或对象中的方法,属性可以向下传递。
js 使用了构造函数来创建对象,如下,我们可以通过构造函数来定义一个类:
可以看到这个类除了我们定义的两个属性以外,还有一个 prototype 的属性。prototype 指向函数的原型对象,这是一个显式原型属性,只有函数才拥有该属性。
prototype 也拥有两个属性:
constructor:指向原型的构造函数
prototype:指向了 Object 的原型
在 prototype 的属性中有一个_proto_,那么这个_proto_与 prototype 又有什么关系?
原型 prototype 是类的一个属性,而所有用类实例化的对象,都将拥有这个属性中的所有内容,包括变量和方法。比如上图中的 p 对象,其天生就具有类 Person 的属性和方法。
我们可以通过 Person.prototype 来访问 Person 类的原型,但 Person 实例化出来的对象,是不能通过 prototype 访问原型的。这时候,就该__proto__登场了。
也就是类可以用 prototype 来访问类的原型,而实例化的对象可以用_proto_来访问对象所在类的 prototype 属性。
总结:
其实我们只要明白一点就可以了,JavaScript 原型链是 js 中实现继承的核心,js 的对象都会执行其它的原型,最后指向的原型为 null。最后关于原型链再来总结一下。
1)js 是通过原型链来实现继承的。
2)所有类对象在实例化的时候将会拥有 prototype 中的属性和方法
3)类可以使用 prototype 来访问类的原型对象,而实例化对象可以通过_proto_来访问类的原型对象
原型链污染
在了解了原型链的相关知识以后,可以来看看竟然什么是原型链污染漏洞。
上面说过实例化对象的__proto__指向了类的 prototype。那么,如果我们修改了实例化对象__proto__中的值,是不是就可以修改类中的值呢?是否可以影响所有和这个对象来自同一个类、父祖类的对象?
其实这就是原型链污染的原理。我们通过修改实例化对象的__proto__中的值,污染了类本体,进而影响所有和这个对象来自同一个类、父祖类的对象。
p 神的博客上有一个这样的例子,用来说明原型链污染:
实际情况下利用分析
在实际的情况中,我们可能只能控制部分参数,那么我们怎么才能为__proto__赋值呢?
要为__proto__赋值就要求__proto__作为变量传进去并且作为键名,这种情况一般出现在下面的三种场景中:
对象 merge
对象 clone(其实内核就是将待操作的对象 merge 到一个空对象中)
路径查找属性然后修改属性的时候
下面借用 p 神文章中的一个例子来看看具体操作,原文链接为:https://www.leavesongs.com/PENETRATION/javascript-prototype-pollution-attack.html
以对象 merge 为例,我们想象一个简单的 merge 函数:
在合并的过程中,存在赋值的操作 target[key] = source[key],那么,这个 key 如果是__proto__,是不是就可以原型链污染呢?
结果是,合并虽然成功了,但原型链没有被污染:
这是因为,我们用 JavaScript 创建 o2 的过程(let o2 = {a: 1, "__proto__": {b: 2}})中,__proto__已经代表 o2 的原型了,此时遍历 o2 的所有键名,你拿到的是[a, b],__proto__并不是一个 key,自然也不会修改 Object 的原型。从下面的图中也可以看出,在 o1 中,参数 b 并没有出现在原型中。
那么,如何让__proto__被认为是一个键名呢?
我们将代码改成如下:
可见,新建的 o3 对象,也存在 b 属性,说明 Object 已经被污染
这是因为,JSON 解析的情况下,__proto__会被认为是一个真正的“键名”,而不代表“原型”,所以在遍历 o2 的时候会存在这个键。
再来看看 o1 发现,b 属性是定义在原型之中的。
merge 操作是最常见可能控制键名的操作,也最能被原型链攻击,很多常见的库都存在这个问题。
js 原型链污染漏洞分析
接下来用一个 cve 漏洞来具体再看一下这个漏洞。
3.4.0 版本之前的 jQuery 存在一个原型污染漏洞 CVE-2019-11358,PoC 如下。
jQuery.extend () 函数用于将一个或多个对象的内容合并到目标对象
$.extend( [deep ], target, object1 [, objectN ] )
再来看看实际的代码是如何去写入的。
首先下载 jQuery,这里下载的是 3.3.0 版本
https://github.com/jquery/jquery/tree/3.3.0
在 src/core.js 中文件中可以找到该 extend 函数。看过源码,可以发现该函数的正好符合上面说的合并数据的概念,那么来看看它到底会不会被污染?
我们来动态调试一下这个程序:
引入 jQuery 脚本,并设置断点,进行调试
首先根据第一个参数判断是否进行深度拷贝,然后进行第一次循环,取得参数为__proto__
第二次循环,在__proto__中去参数进行赋值
此时再看原型已经被污染了
总结
js 原型链污染可以说原理并不是太难懂,关键是实际中如何去利用。关于这个漏洞也是看了很多大神的文章,它们的思路真的太厉害了,我还有很多需要学习的,跟大家一起共勉。
由于本人水平有限,文章中可能会出现一些错误,欢迎各位大佬指正,感激不尽。如果有什么好的想法也欢迎交流,谢谢大家了~~
参考链接
https://www.freebuf.com/articles/web/275619.html
https://www.leavesongs.com/PENETRATION/javascript-prototype-pollution-attack.html
版权声明: 本文为 InfoQ 作者【互联网工科生】的原创文章。
原文链接:【http://xie.infoq.cn/article/084b3c0c7e178bef2c07ece9f】。
本文遵守【CC-BY 4.0】协议,转载请保留原文出处及本版权声明。
评论