ECMAScript浅析 Node.js 的 vm 模块和运行不信任代码

定制 context 与安全题材

在一个全新的 V8
context
 里运行代码,里面富含了言语专业规定的停放的片函数和目标,假使大家记忆要有些言语专业以外的意义如故模块,我们得拿相应对象放置跟是
context 关联的对象里,例如当上头例子中之霎时词代码:

const sandbox = {globalVar: 1, setTimeout: setTimeout, cb: function(result) {  
    console.log(result);
}};

 set提姆eout  不是语言专业规定之坐函数,
context 本身不提供,所以大家用通过涉及的目标传上。

 

而,当大家管一些模块效率提供于 context
的时光,也还要辅导了双重多的安全隐患,请看下来例子:

const util = require('util');  
const vm = require('vm');

const sandbox = {};  
vm.createContext(sandbox);  
const script = new vm.Script(`  
    // sandbox 的 constructor 是外层的 Object 类
    // Object 类的 constructor 是外层的 Function 类
    const OutFunction = this.constructor.constructor;
    // 于是, 利用外层的 Function 构造一个函数就可以得到外层的全局 this
    const OutThis = (OutFunction('return this;'))();
    // 得到 require
    const require = OutThis.process.mainModule.require;
    // 试试
    require('fs');
`,{});
const result = script.runInContext(sandbox);  
console.log(result === require('fs'));  
// true

 

不问可知,定制 context
的时段,任何一个传染进的目的或函数都可能带来下面的问题,安全题材确实发生这一个行事要开。

 

Github
上发一部分开源之模块用于周转不信任代码,例如 sandboxvm2jailed顶。查看这个项目之
issue 可以窥见,sandbox 和 jailed 都足以为此接近下边的章程突破限制,而 vm2
针对性当时面开了防范,其余方面为召开了再一次多之平安工作,相对安全些。

 

产被得考虑在子进程中运行 vm2, 然后加又低层的拉萨范围,
例如限制进程的权能和应用 cgroups 举办IO,内存等资源限制,这里不详细探究。

 

当有网受,我们愿意为用户提供插入自定义逻辑的能力,除了  RPC  和  REST  之外,运行客户提供的代码也是较常用之措施,好处是足以大幅度地收缩在网达到的耗时。JavaScript
是平种分外流行而爱上手的语言,因而,让用户之所以 JavaScript
来描写于定义逻辑是一个科学的挑。下面我们介绍 Node.js 提供的 vm 模块和分析就此它来运作不信任代码可能撞的题材。

vm 模块

vm 模块是 Node.js 内置的主干模块,它可以给大家编译 JavaScript
代码和于指定的条件受到运作。

要圈下面例子:

const util = require('util');  
const vm = require('vm');

// 1. 创建一个 vm.Script 实例, 编译要执行的代码
const script = new vm.Script('globalVar += 1; anotherGlobalVar = 1; ');  
// 2. 用于绑定到 context 的对象
const sandbox = {globalVar: 1};  
// 3. 创建一个 context, 并且把 sandbox 这个对象绑定到这个环境, 作为全局对象
const contextifiedSandbox = vm.createContext(sandbox);  
// 4. 运行上面编译的代码, context 是 contextifiedSandbox
const result = script.runInContext(contextifiedSandbox);

console.log(`sandbox === contextifiedSandbox ? ${sandbox === contextifiedSandbox}`);  
// sandbox === contextifiedSandbox ? true
console.log(`sandbox: ${util.inspect(sandbox)}`);  
// sandbox: { globalVar: 2, anotherGlobalVar: 1 }
console.log(`result: ${util.inspect(result)}`);  
// result: 1

 

 vm.Script  是一个近乎,用于创建代码实例,前面可以屡屡运作。

 

 vm.createContext(sandbox)  用于
“contextify” 一个靶,依据 ECMAScript 2015
语言专业
,代码的实施得一个 execution
context
。这里的
“contextify”,就是管染进的靶子以及 V8
的一个初的 context 举办关联。那里所说之关联,我的接头是,那一个”contextified” 对象的属性将会师成为这么些 context 的全局属性,同时,在
context 下运作代码时发的全局属性为会面化为这一个 “contextified”
对象的习性。

 

 script.runInContext(contextifiedSandbox)  就是设代码在  contextifiedSandbox  这多少个 context
中运行,从点的输出可以看出,代码运行后, contextifiedSandbox 里面的特性的值都为更改了,运行结果是最终一个表明式的价。

 

除了下面几乎只接口之外,vm 模块还有有再一次便捷的接口,例如  vm.runInContext(code, contextifiedSandbox[,
options]) , vm.runInNewContext(code[, sandbox][,
options]) 等,详细可看文档

 

代码运行时间范围

 script.runInContext(contextifiedSandbox[,
options])  方法有一个  timeout  选项可以设定代码的运转时,要是领先时间就算会见扔来荒唐,请看下例子: 

const util = require('util');  
const vm = require('vm');  
const sandbox = {};  
const contextifiedSandbox = vm.createContext(sandbox);  
const script = new vm.Script('while(true){}');  
const result = script.runInContext(contextifiedSandbox, {timeout: 1000});  
// const result = script.runInContext(contextifiedSandbox, {timeout: 1000});
//                       ^
// Error: Script execution timed out.

 再尝试异步代码,

const util = require('util');  
const vm = require('vm');

const sandbox = {globalVar: 1, setTimeout: setTimeout, cb: function(result) {  
    console.log(result);
}};
vm.createContext(sandbox);  
const script = new vm.Script(`  
    setTimeout(function(){
        globalVar++;
        cb("async result");
    }, 1000);
    globalVar;
`,{});
const result = script.runInContext(sandbox, {timeout: 500});  
console.log(`result: ${result}`);  
// result: 1
// async result

没有不当抛来,也就是说,这么些选项并无可以限制异步代码的运作时刻,这该怎么去界定有代码的举行时间吧,方今相近没有接口终止
vm
代码的运作,如果出异步代码长日子不了事,很轻造成内存泄露,最近有效的方案是使用子进程去运作代码,尽管超过限定时间还尚未结果,就杀掉该子进程,此外,使用子进程还得更便于地对内存等资源开展限制。

 

参考

vm 
Allowing to terminate a vm
context/script
 
V8 Embedder’s
Guide
 
ECMAScript 2015
语言专业
 
sandbox/issues/50 
vm2/issues/32 
jailed/issues/33 
cgroups

外层怎么样拿到代码运行结果

咱因此 vm
运行代码的时怪可能要得到有结实,从点的例证中雅观看,大家可因此将结果作为最终一个表明式的价传被外层,或者当作 context 的性为外层使用,这在同步代码里无问题,不过假设结果用看重里面的异步操作为?这时,我们可通过在  context  里放一个回调函数。

脚是例证:

const util = require('util');  
const vm = require('vm');

const sandbox = {globalVar: 1, setTimeout: setTimeout, cb: function(result) {  
    console.log(result);
}};
vm.createContext(sandbox);  
const script = new vm.Script(`  
    setTimeout(function(){
        globalVar++;
        cb("async result");
    }, 1000);
`,{});
script.runInContext(sandbox);  
console.log(`globalVar: ${sandbox.globalVar}`);  
// globalVar: 1
// async result

 

总结

本文通过几独例证介绍了 Node.js 的 vm 模块和拔取 vm
模块运行不信任代码可能撞的题目,并且针对平安问题吃来了片提出。