VM2 VMScript预编译

简介

vm2是一个沙箱,可以使用列入白名单的Node的内置模块运行不受信任的代码。安全!

特征

  • 使用您的代码并排在一个进程中安全地运行不受信任的代码
  • 完全控制沙盒的控制台输出
  • Sandbox对进程方法的访问权限有限
  • 沙箱可以要求模块(内置和外置)
  • 您可以限制对某些(或所有)内置模块的访问
  • 您可以安全地调用方法并在沙箱之间交换数据和回调
  • 免疫while (true) {}(这点应该是vm2可以设置超时时间,防止死循环一直执行)
  • 对所有已知的攻击方法都免疫
  • 运营商支持

它是如何工作的

  • 它使用内部VM模块来创建安全上下文
  • 它使用Proxies来防止逃离沙箱
  • 它会覆盖builtin require来控制对模块的访问

Node的vm和vm2有什么区别?

const vm = require('vm');
vm.runInNewContext(
    'this.constructor.constructor("return process")().exit()'
);
console.log('Never gets executed.');
const {VM} = require('vm2');
new VM().run(
    'this.constructor.constructor("return process")().exit()'
);
// Throws ReferenceError: process is not defined

安装

npm i vm2

使用场景

  在项目中需要处理用户输入的内容,限制用户可使用的模块,还需要对用户输入的内容检查错误,并及时反馈给用户。针对以上场景,选择使用vm2 模块中的 NodeVM 搭配 VMScript ,接下来做一个简单的介绍。

NodeVM

NodeVM允许在常规Node的上下文中使用相同的模块。
Option

  • console- inherit启用控制台,redirect重定向到事件,off禁用控制台(默认值:) inherit。
  • sandbox - VM的全局对象。
  • compiler- javascript(默认)或coffeescript自定义编译器函数(接收代码及其文件路径)。
  • eval-如果设置为false任何来电eval或函数的构造函数(Function,GeneratorFunction,等)将抛出一个EvalError(默认:true)。
  • wasm- 如果设置为false任何尝试编译WebAssembly模块将抛出WebAssembly.CompileError(默认值:) true。
  • sourceExtensions- 要作为源代码处理的文件扩展名数组(默认值:) [‘js’]。
  • require- true或对象启用require方法(默认值:) false。
  • require.external- true,允许的外部模块或对象的数组(默认值:) false。
  • require.external.modules - 允许的外部模块阵列。还支持通配符,因此指定[’@scope/*-ver-??],例如,将允许使用具有以下形式的名称的所有模块@scope/something-ver-aa,@scope/other-ver-11等等。
  • require.external.transitive- 布尔值,指示是否允许外部模块的传递依赖性(默认值:) false。
  • require.builtin - 允许内置模块的数组,接受所有的[“*”](默认值:无)。
  • require.root - 设置本地模块可被访问的路径(默认值:每个路径)。
  • require.mock - 模拟模块的集合(外部或内置)。
  • require.context- host(默认)要求主机中的模块并将它们代理到沙箱。sandbox在沙箱中加载,编译和需要模块。除了events在主机中始终需要并代理到沙箱之外的内置模块。
  • require.import - 启动时要加载到NodeVM的模块数组。
  • require.resolve - 如果在其中一个传统节点查找路径中找不到模块,则需要额外的查找功能。
  • nesting- true启用VM嵌套(默认值:) false。
  • wrapper- commonjs(默认)将脚本包装到CommonJS包装器中,none 检索脚本返回的值。

重要提示:超时对NodeVM无效,因此它不会免疫while (true) {}或类似的问题。

VMScript

  你可以使用预编译的脚本来提高性能。预编译的VMScript可以多次运行。重要的是要注意代码没有绑定到任何VM(上下文); 相反,它在每次运行之前被绑定,仅用于该运行。(目前测试这个预编译只能检测出语法错误)

// script.js 在如下脚本存在两个错误:
// console拼写错错误,在括号里面hello右单引号丢失,
module.exports = function(){
    consle.log('hello);
}
const {NodeVM, VMScript} = require('vm2');
const fs = require('fs');
const file = `${__dirname}\\script.js`;

const vm = new NodeVM({
    console: 'inherit',
    sandbox: {},
    require: {
        external: true,
        builtin: ['fs', 'path'],
    }
});
const script = new VMScript(fs.readFileSync(file), file);
script.compile()

  在执行只能检测到拼写错误,缺少符号;如果把符号补上,就不会报错;检测不出使用模块未声明的错误。

错误处理

  在运行沙箱中的代码时,最好用try/catch 进行包裹,这样出现问题也不会导致全局崩溃,代码编译和同步代码执行中的错误可由try/ catch 处理。可以通过将uncaughtException事件处理程序附加到Node 来处理异步代码执行中的错误process。

try {
    var script = new VMScript("Math.random()").compile();
} catch (err) {
    console.error('Failed to compile script.', err);
}

try {
    vm.run(script);
} catch (err) {
    console.error('Failed to execute script.', err);
}

process.on('uncaughtException', (err) => {
    console.error('Asynchronous error caught.', err);
})

实例

这个实例主要使用了 vm2 如下一些常用功能:

  1. 打印功能 console
  2. 全局变量 sandbox
  3. 允许使用第三方库require.builtin
  4. 只读权限限制 freeze
// script.js
// 在此实例中使用了 sandbox 中的 module 和 crypto 两个模块,require 里的参数选项并没使用到。
module.exports = function () {
    const crypto = require('crypto')
    const secret = 'abcdefg';
    const hash = crypto.createHmac('sha256', secret)
        .update('I love cupcakes')
        .digest('hex')

    console.log(hash);
    gdata["hash"] = hash;
};

// vm2/test.js

const { NodeVM, VMScript } = require('vm2');
const fs = require('fs');
const file = `${__dirname}/script.js`;

const util = {
	rand () {
		return Math.random() * 10;
	}
}

console.log(file);
console.time('test')
let gdata = {};
const vm = new NodeVM({
	console: 'inherit',
	sandbox: { gdata },
	require: {
		external: true,
		builtin: ['crypto'],
	}
});

try {
	var script = new VMScript(fs.readFileSync(file));
} catch (err) {
	console.error('Failed to compile script.', err);
}

try {
	vm.freeze(util, "util");
	let res = vm.run(script);   
	res();
	console.log(gdata);
} catch (err) {
	console.error('Failed to execute script.', err);
}

process.on('uncaughtException', (err) => {
	console.error('Asynchronous error caught.', err);
})

console.timeEnd('test')
console:
/*
c0fa1bc00531bd78ef38c628449c5102aeabd49b5dc3a2a516ea6ea959d6658e
9.638111982891482
{ hash:
   'c0fa1bc00531bd78ef38c628449c5102aeabd49b5dc3a2a516ea6ea959d6658e',
  randid: 9.638111982891482 }
test: 24.484ms
*/

  • 1
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值