通过两道CTF题学习原型链污染
摘要:粗浅地总结下原型链污染这门攻击技术
0x01写在前面
之前开了个两周学完JS的博客,学到昨天终于学到了原型链部分。之前刷题的时候碰到了原型链污染的题目,当时感觉不是很理解,经过这阵子对js的学习感觉能够理解了,所以回去又将题目再看了遍。在这就特意开一篇博客来粗浅地总结下原型链污染这门攻击技术。
0x02预备知识
JS中的原型以及__proto__和prototype的区别与联系
JavaScript是一门很有意思的语言,在这门语言里,万物都可看做对象。我们知道,在面向对象的语言里面,存在着一种叫做“继承”的东西。比方说在Java中,通过extends关键字让一个类继承另一个类,格式形如:
1 | public class A extends B{ |
那么继承之后呢,B成为A的父类,且A可以继承得到B中的所有属性以及方法(这里不够严谨,比方说父类的private成员变量无法继承)。同样,JavaScript中也有类似的概念。但从前JavaScript的并不存在类的概念——事实上新出来的class也不过是一种语法糖,同java等面向对象的语言中的类有本质上的区别。那么同java等面向对象等语言所不同的地方在于,我们在关注继承的时候,更应该关注对象constructor也就是构造函数,因为在JavaScript中我们是通过构造函数去生成一个实例化的对象,这个构造函数就行相当于类了。
我们开头说了,JavaScript中万物皆对象,同时我们需要知道的是每个对象都有一个proto属性,这个属性指向的是当前对象的原型对象;而函数作为特殊的对象,它有一个特殊的prototype属性,这个属性指向的是以当前函数作为构造函数构造的对象的原型对象。很绕是吧,我们下面慢慢理解。
首先我们来看一下proto这个属性
书写如下代码:
1 | let qmm = [1,2,3,4]; |
使用chrome浏览器控制台查看显示:
注意到数组、对象当中都存在一个__proto__属性。我们看到,我们new出来的或者我们通过字面量形式整出来的对象他的__proto__属性为Object,而数组的为Array(0),这就是我们前面所提的原型对象。很好理解对象的原型对象为Object数组的原型为Array(0)。
好,下面我们来看一下prototype:
书写以下代码:
1 | function GirlFriend(){ |
这边我们创造了一个GirlFriend构造函数,并通过该构造函数实例化了一个对象并在第一行打印了该对象,第二行打印该对象的原型,在第三行打印了构造函数的prototype。
发现了吗,gf.__proto__和GirlFriend.prototype是相等的,不相信的可以用instanceof验证下。回到我一开始说的定义,prototype指向的是以当前函数作为构造函数构造的对象的原型对象。这就很好理解了对吧,同时我们也关注到gf的打印,确实在构造函数中才存在着prototype。
我希望通过这一小节,我把原型这块的概念讲清楚了。
原型链与原型链污染
那么现在我们来看看原型链的概念。回到我们的第一个例子,我们展开数组的__proto__如下:
注意都__proto__展开后里面还有一层__proto__,第一个是Array,下一个是Object。这就是我们的原型链,我们建造的数组qmm的原型是Array,而Array的原型又是Object,这样一直往上形成了一条“原型链”。那么Object往上呢?我们来看看,在代码后面追加一条:
1 | console.log(Object.prototype.__proto__); |
发现是null那么,Object就是原型链的头了,所以js中万物皆对象也好理解了。
原型链的存在提供了这样一种机制,一个对象可以调用原型链以上的他的父辈的所有的方法与属性,这就很有意思了,我们来试试:
1 | let qmm = [1,2,3,4]; |
我在中间加了这样一条语句
1 | obj.__proto__.nnmp = "lalala"; |
根据上面所讲的obj.__proto__为Object那么我们相当于在Object处加了一个名为nnmp的属性。会发生什么呢?
发现了吗,只要原型为Object的都拥有这个属性,如果我们尝试调用我们发现也是可行的,我们传入了nnmp这个无意义的属性,从而对原型链产生了“污染”。那进一步思考,如果我们传入一个函数,不就可以进行任意代码执行了吗?
原型链污染攻击
那么我们应该如何利用呢?我才疏学浅,还是个小白,目前接触到的利用方式主要有两种。一种是源码中出现形如
1 | let vul[a][b] = obj; |
的形式,这个时候,a、b、obj三个参数都要可控,才可以为原型中注入我们想要执行的命令,上面我们用的就是这种方式。
一种是通过merge函数或者其他的类似的可以操控键名的函数。merge操作是最常见可能控制键名的操作,也最能被原型链攻击。我们来看一个引用自Node.js 常见漏洞学习与总结这篇文章的例子
1 | function merge(target, source) { |
执行结果如下
我们发现,object3也存在了一个b属性。这个原理和上面讲的别无二致。这边需要注意的一个点是JSON.parese这个点,之所以要有这个操作是为了让merge在整合的时候把__proto__作为一个键整合,这就是js机制的问题了。
0x03实例
GYCTF2020 Ez_Express
不墨迹,直接看存在原型链污染的点。
注意到clone这个函数,他讲我们传入的data给整合到req这个对象中来,那req中是否存在一个“属性”能让我们利用呢?
注意到/路由中的outputFunctionName为undefine,那这个属性又是什么来头呢?事实上,根据题目的提示,这是一个express框架下存在的一个rce漏洞利用点。具体见这篇文章
Express+lodash+ejs: 从原型链污染到RCE
也就是说,只要我们对这个outputFunctionName赋上一个我们想要执行恶意函数,就可以执行我们想要执行的命令了。ok根据我们的分析,书写payload
1 | {"lua":"hn13","__proto__":{"outputFunctionName":"a;return global.process.mainModule.constructor._load('child_process').execSync('cat /flag'); //"} |
构造请求包发送得到flag。
还有一题就是P牛在18年Code-Breaking出的一道题。本人没时间复现了,具体看p牛的文章和其他的参考文章吧。
深入理解 JavaScript Prototype 污染攻击
Code Breaking 挑战赛 Writeup
0x04写在后面
JS这个特性确实有意思,感觉理解起来也还算好理解。其实看懂确实很容易,但真的要去挖掘这类似的漏洞的话,就得有一定的功底了——代码审计啊、对语言特性的理解啊等等,唉,自己还是太菜了。还是好好加油吧。文章写得不是很好,希望路过的师傅看到有不足之处请指正!
- 本文标题:通过两道CTF题学习原型链污染
- 本文作者:Hn13
- 本文链接:https://www.hn13.top/2020/04/18/通过两道CTF题学习原型链污染/>
- 发布时间:2020-04-18
- 版权声明:本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明出处!