关于沙箱与nodejs vm/vm2

什么是沙箱

我的世界是一款现象级沙盒类游戏,玩家可以在自己的世界随意创造,用自己的想象力搭建一个虚拟的,完全与外界隔离的世界。

显而易见,沙箱sandbox就是一种虚拟隔离的安全机制,其中的应用与代码能够和外界全局变量隔绝(理想情况)。

常见的例子就是docker和虚拟机VM。

沙箱用到了三种技术:虚拟化技术访问控制技术和躲避技术。
这里着重讲一下虚拟化技术。

虚拟化技术(Virtualization)是一种资源管理技术。通过虚拟化,计算机的很多实体资源,包括CPU、内存、磁盘空间等,都会被抽象化后成为可供分割和重新组合的状态,让用户可以自己重新分配电脑硬件资源。如图所示,在沙箱中会使用虚拟化技术为不可信的资源构建封闭的运行环境,在保证不可信资源功能正常运行的同时提供安全防护。简而言之,即是沙箱中被隔离的可疑或待测程序会使用沙箱中的资源运行,以保证沙箱外资源的安全,不影响沙箱外其他程序的运行。

虚拟化技术

关于沙箱的一些特点

沙箱根据虚拟化层次的不同,分为系统级别和容器级别。前者有一整套虚拟的操作系统,是一个完整的计算机软件架构,而后者则是部署在应用周围的一个容器(container)。沙箱的重点在于安全测试,即其跟其他通常意义上的容器有所不同,侧重点在于不让沙箱内部影响沙箱外部。

沙箱在部署上还分为硬件沙箱和云沙箱

nodejs vn/vm2

关于nodejs的作用域和几个API

作用域

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#1.js
var height1 = 175
exports.height = height1

#2.js
const age = 20
const user = require("./1")

console.log(age)
console.log(user.height)

//20
//175

height的作用域是1.js,age的作用域是2.js。但是exports是一个能将js文件中元素输出的接口,故在2.js中,常量user通过require方法得到1.js里的所有元素。

(是不是很像python的库概念)

同时,在这连个js文件之上还有一个global全局作用域,nodejs全部的包和属性都挂载在global这个对象下。跟py里import*一个道理,可以直接访问。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 1.js
var height1 = 175
global.name = "ThnPkm"

exports.height = height1



# 2.js
const age = 20
const user = require("./1")

console.log(age)
console.log(user.height)
console.log(name)


关于vm/vm2沙箱

即船舰一个新的作用域去执行我们想要测试的代码,在nodejs中,作用域又称为上下文

在介绍几个vm的api前,先科普一下api的概念
API(即应用程序编程接口)是一组定义的规则,使不同的应用程序能够相互通信。它充当处理系统之间数据传输的中间层,使公司能够向外部第三方开发人员、业务合作伙伴和公司内部部门开放其应用程序数据和功能²³。

具体来说,API有以下几个关键点:

  1. 程序之间的接口:API允许不同的应用程序、服务或系统之间共享信息和功能,以约定好的接口实现互联互通。
  2. 形式多样:API不仅仅是函数,还可以是类、HTTP网络请求等形式。
  3. 网络请求为基础:现代互联网中,以网络请求为基础的API是最常见的形式。这些API允许不同公司之间通过网络进行数据交换,比如调用滴滴的打车服务、获取天气数据等。

https://zhuanlan.zhihu.com/p/144399137.
https://www.zhihu.com/question/21430743.
http://www.didi.com/taxi.

vm.runinThisContext(code)

在当前global下创建一个作用域(sandbox),并将接收到的参数当作代码运行。sandbox中可以访问到global中的属性,但无法访问其他包中的属性。

1
2
3
4
5
6
7
8
# xxx.js
const vm = require('vm');
const local_var = "local";
const vm_var = vm.runInThisContext('local_var = "vm";');

console.log(vm_var); //vm
console.log(local_var); //local
//local_var 的值没变,即这个runAPI接口无法访问本地作用域

换句话说,这个沙箱成功隔离了,沙箱内对于变量的操作没有污染到挂载到全局变量的变量。

vm.createContext([sandbox]):

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

vm3

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

配合creatcontext使用

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

1
2
3
4
5
6
7
8
9
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

这里,上下文中的globalVar在输出中为(2*2 = 4),但是globalVar的值仍为1,沙箱内部无法访问global中的属性。

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

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

vm.Script类

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

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对象可以通过runInContext运行

vm中最关键的就是 上下文context ,vm能逃逸出来的原理也就是因为 context 并没有拦截针对外部的 constructor 和 __proto__等属性 的访问

沙箱逃逸

参考文章:
https://info.support.huawei.com/info-finder/encyclopedia/zh/%E6%B2%99%E7%AE%B1%E6%8A%80%E6%9C%AF.html
https://blog.csdn.net/qq_61768489/article/details/130138439
https://www.anquanke.com/post/id/207283