NodeJS vm&vm2沙箱逃逸

本文介绍了Node.js中的沙箱概念,特别是vm和vm2模块如何创建隔离作用域,以及如何通过vm模块进行沙箱逃逸,包括利用构造器链、proxy特性以及模板字符串绕过限制。文章还提供了示例和实际逃逸挑战的解决方案。

NodeJS vm&vm2沙箱逃逸

什么是沙箱?

在讲沙箱逃逸之前,我们需要了解一下什么是沙箱。

沙箱就是一个集装箱,把你的应用装到沙箱里去运行,这样应用与应用之间就产生了边界,不会相互影响。

当我们运行一些可能产生危害的程序时,我们不能直接在主机上运行。我们可以单独开辟一个运行代码的环境,这个环境就是沙箱,它与主机相互隔离,但使用主机的资源,但有危害的代码在沙箱内运行只会对沙箱内部产生一些影响,不影响主机的功能。

Docker属于沙箱SandBox的一种,通过创建一个有边界的运行环境将程序放在里面,使程序被边界困住,从而使程序与程序之间,程序与主机之间相互隔离开。

NodeJS的作用域

我们在写Node项目时,往往需要require其他js文件,我们把这些文件称为:包。包之间的作用域是相互隔离不互通的,也就是说,就算我们在y2.js中require了y1.js,我们在y2.js中也无法使用y1.js中的变量和函数。(在Node中一般把作用域叫上下文)

如果想要使用的话,必须使用exports这个nodeJS中将文件元素输出的接口。

exports是将文件元素输出的接口

举个例子:

// y2.js
const file = require('./y1.js')
console.log(file.name)

// y1.js
let name = 'y1.js'


// 输出:undefined
// y2.js
const file = require('./y1.js')
console.log(file.name)

// y1.js
let name = 'y1.js' 
exports.name = name

// 输出:y1.js

由此可知,我们使用require引用其他文件后,想要使用其中的变量,方法之一就是使用exports将该元素导出

image-20230805104257865

此时两个包的关系就是上面这样

global全局对象

除了上面这种方法,我们还可以使用global作用域,也就是global全局对象。NodeJS下其他所有的属性和包都挂载在这个global对象下面,在global下面挂载了一些全局变量,我们访问不需要global.xxx的方式访问,可以直接使用。例如:console就是这个global下的一个全局变量,我们可以直接使用,process也是global下的一个全局变量(一会逃逸要用到)

除了自带的全局变量,我们也可以使用global关键字自己声明一个全局变量

// 1.js
const file = require('./2.js')
console.log(name)

// 2.js
global.name = 'leekos'

// 输出:leekos

可见,我们输出name时,不需要使用file.name的形式,我们可以直接使用name进行输出,同时name也不需要使用exports进行导出,因为此时name已经挂载在global上了,它的作用域不在2.js中了

vm沙箱模块

我们前面提到了作用域(上下文)这个概念,如果我们想要实现沙箱的隔离作用,我们是不是可以创建一个新的作用域,让代码在这个新的作用域中运行,这样就与其他作用域隔离了,这就是vm模块的原理。下面我们介绍几个vm模块的api:

vm.runInThisContext(code)

vm.runinThisContext(code):在当前的global下创建一个作用域(sandbox),并将接收到的参数当做代码执行。

sandbox沙箱中可以访问到global中的属性,但是无法访问其他包的属性。(无法访问本地的属性)

他们之间的关系就是这样:

image-20230805111203380

sandbox可以访问global中的属性,但是不能访问xxx.js中的属性

// xxx.js
const vm = require('vm')
var local_var = 'leekos'
global.global_var = 'xxx global~'
var vm_var = vm.runInThisContext('global_var="vm_var";local_var="Tranquility";')
console.log("vm_var: "+vm_var)
console.log("local_var: "+local_var)
console.log(global_var)


/*
输出:
vm_var: Tranquility
local_var: leekos
vm_var
*/

由此可见vm.runInThisContext()在当前文件的作用域外创建了一个新的作用域,并且该作用域在global作用域之中

此时vm.runInThisContext()xxx.js处在不同的作用域之中,因此也不能改变local_var变量的值,但是由于vm.runInThisContext()在global作用域中,因此可以改变global_var的值

vm.createContext([sandbox])

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

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

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

image-20230805115658208

const vm = require('vm')
global.global_var = 1
const sandbox = {
   
   global_var: 2} //创建一个沙箱对象
vm.createContext(sandbox)  //创建一个上下文对象
vm.runInContext('global_var*=2',sandbox)

console.log(sandbox)    // { global_var: 4 }
console.log(global_var) // 1

这种创建方式与vm.runInThisContext()有区别,这种不能改变global中的全局变量的值,沙箱内部无法访问global中的属性

(因为在当前的global外再创建了一个作用域)

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

这个函数是createContext()runInContext()的结合版,传入要执行的代码和沙箱对象

vm.Script类

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

new vm.Script(code, options)

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

const vm = require('vm')
const sandbox = {
   
   animal: 'cat',count: 1}
const script = new vm.Script('count += 1; name = "Tom";') // 编译code
const context = vm.createContext(sandbox) // 创建一个上下文对象
script.runInContext(context)  // 在指定的上下文中执行code并返回结果

console.log(sandbox) // { animal: 'cat', count: 2, name: 'Tom' }

script对象可以通过runInXXXContext运行

vm能逃逸出来的原理是因为context没有拦截对外部的constructor__proto__等属性的访问

如何进行vm沙箱逃逸?

我们一般进行沙箱逃逸最后都是RCE,那么在NodeJS中进行RCE就需要使用process全局变量了,在获取到process对象之后我们就可以使用require来导入child_process,利用它来执行命令

例如:

console.log(process.mainModule.require('child_process').execSync('whoami').toString())

// leekos\like
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值