浅谈Node.js 沙箱环境


Posted in Javascript onMay 15, 2018

node官方文档里提到node的vm模块可以用来做沙箱环境执行代码,对代码的上下文环境做隔离。

\A common use case is to run the code in a sandboxed environment. The sandboxed code uses a different V8 Context, meaning that it has a different global object than the rest of the code.

先看一个例子

const vm = require('vm');
let a = 1;
var result = vm.runInNewContext('var b = 2; a = 3; a + b;', {a});
console.log(result);  // 5
console.log(a);     // 1
console.log(typeof b); // undefined

沙箱环境中执行的代码对于外部代码没有产生任何影响,无论是新声明的变量b,还是重新赋值的变量a。 注意最后一行的代码默认会被加上return关键字,因此无需手动添加,一旦添加的话不会静默忽略,而是执行报错。

const vm = require('vm');
let a = 1;
var result = vm.runInNewContext('var b = 2; a = 3; return a + b;', {a});
console.log(result);
console.log(a);
console.log(typeof b);

如下所示

evalmachine.<anonymous>:1
var b = 2; a = 3; return a + b;
         ^^^^^^

SyntaxError: Illegal return statement
  at new Script (vm.js:74:7)
  at createScript (vm.js:246:10)
  at Object.runInNewContext (vm.js:291:10)
  at Object.<anonymous> (/Users/xiji/workspace/learn/script.js:3:17)
  at Module._compile (internal/modules/cjs/loader.js:678:30)
  at Object.Module._extensions..js (internal/modules/cjs/loader.js:689:10)
  at Module.load (internal/modules/cjs/loader.js:589:32)
  at tryModuleLoad (internal/modules/cjs/loader.js:528:12)
  at Function.Module._load (internal/modules/cjs/loader.js:520:3)
  at Function.Module.runMain (internal/modules/cjs/loader.js:719:10)

除了runInNewContext外,vm还提供了runInThisContext和runInContext两个方法都可以用来执行代码 runInThisContext无法指定context

const vm = require('vm');
let localVar = 'initial value';​
const vmResult = vm.runInThisContext('localVar += "vm";');
console.log('vmResult:', vmResult);
console.log('localVar:', localVar);
console.log(global.localVar);

由于无法访问本地的作用域,只能访问到当前的global对象,因此上面的代码会因为找不到localVal而报错

evalmachine.<anonymous>:1
localVar += "vm";
^

ReferenceError: localVar is not defined
  at evalmachine.<anonymous>:1:1
  at Script.runInThisContext (vm.js:91:20)
  at Object.runInThisContext (vm.js:298:38)
  at Object.<anonymous> (/Users/xiji/workspace/learn/script.js:3:21)
  at Module._compile (internal/modules/cjs/loader.js:678:30)
  at Object.Module._extensions..js (internal/modules/cjs/loader.js:689:10)
  at Module.load (internal/modules/cjs/loader.js:589:32)
  at tryModuleLoad (internal/modules/cjs/loader.js:528:12)
  at Function.Module._load (internal/modules/cjs/loader.js:520:3)
  at Function.Module.runMain (internal/modules/cjs/loader.js:719:10)

如果我们把要执行的代码改成直接赋值的话就可以正常运行了,但是也产生了全局污染(全局的localVar变量)

const vm = require('vm');
let localVar = 'initial value';​
const vmResult = vm.runInThisContext('localVar = "vm";');
console.log('vmResult:', vmResult);  // vm
console.log('localVar:', localVar);  // initial value
console.log(global.localVar);     // vm

runInContext在传入context参数上与runInNewContext有所区别 runInContext传入的context对象不为空而且必须是经vm.createContext()处理过的,否则会报错。 runInNewContext的context参数是非必须的,而且无需经过vm.createContext处理。 runInNewContext和runInContext因为有指定context,所以不会向runInThisContext那样产生全局污染(不会产生全局的localVar变量)

const vm = require('vm');
let localVar = 'initial value';​
const vmResult = vm.runInNewContext('localVar = "vm";');
console.log('vmResult:', vmResult);  // vm
console.log('localVar:', localVar);  // initial value
console.log(global.localVar);     // undefined

当需要一个沙箱环境执行多个脚本片段的时候,可以通过多次调用runInContext方法但是传入同一个vm.createContext()返回值实现。

超时控制及错误捕获

vm针对要执行的代码提供了超时机制,通过指定timeout参数即可以runInThisContext为例

const vm = require('vm');
let localVar = 'initial value';​
const vmResult = vm.runInThisContext('while(true) { 1 }; localVar = "vm";', { timeout: 1000});
vm.js:91
   return super.runInThisContext(...args);
          ^

Error: Script execution timed out.
  at Script.runInThisContext (vm.js:91:20)
  at Object.runInThisContext (vm.js:298:38)
  at Object.<anonymous> (/Users/xiji/workspace/learn/script.js:3:21)
  at Module._compile (internal/modules/cjs/loader.js:678:30)
  at Object.Module._extensions..js (internal/modules/cjs/loader.js:689:10)
  at Module.load (internal/modules/cjs/loader.js:589:32)
  at tryModuleLoad (internal/modules/cjs/loader.js:528:12)
  at Function.Module._load (internal/modules/cjs/loader.js:520:3)
  at Function.Module.runMain (internal/modules/cjs/loader.js:719:10)
  at startup (internal/bootstrap/node.js:228:19)

可以通过try catch来捕获代码错误

const vm = require('vm');
let localVar = 'initial value';​
try { 
  const vmResult = vm.runInThisContext('while(true) { 1 }; localVar = "vm";', {
    timeout: 1000
  });
} catch(e) { 
  console.error('executed code timeout');
}

延迟执行

vm除了即时执行代码之外,也可以先编译然后过一段时间再执行,这就需要提到vm.Script了。其实无论是runInNewContext、runInThisContext还是runInThisContext,背后其实都创建了Script,从之前的报错信息就可以看出来 接下来我们就用vm.Script来重写本文开头的例子

const vm = require('vm');
let a = 1;
var script = new vm.Script('var b = 2; a = 3; a + b;');
setTimeout(() => { 
  let result = script.runInNewContext({a}); 
  console.log(result);   // 5 
  console.log(a);     // 1 
  console.log(typeof b);  // undefined
}, 300);

除了vm.Script,node在9.6版本中新增了vm.Module也可以做到延迟执行,vm.Module主要用来支持ES6 module,而且它的context在创建的时候就已经绑定好了,关于vm.Module目前还需要在命令行使用flag来启用支持

node --experimental-vm-module index.js

vm作为沙箱环境安全吗?

vm相对于eval来说更安全一些,因为它隔离了当前的上下文环境了,但是尽管如此依然可以访问标准的JS API和全局的NodeJS环境,因此vm并不安全,这个在官方文档里就提到了

The vm module is not a security mechanism. Do not use it to run untrusted code

请看下面的例子

const vm = require('vm');
vm.runInNewContext("this.constructor.constructor('return process')().exit()")
console.log("The app goes on...") // 永远不会输出

为了避免上面这种情况,可以将上下文简化成只包含基本类型,如下所示

let ctx = Object.create(null);
ctx.a = 1; // ctx上不能包含引用类型的属性
vm.runInNewContext("this.constructor.constructor('return process')().exit()", ctx);

针对原生vm存在的这个问题,有人开发了vm2包,可以避免上述问题,但是也不能说vm2就一定是安全的

const {VM} = require('vm2');
new VM().run('this.constructor.constructor("return process")().exit()');

虽然执行上述代码没有问题,但是由于vm2的timeout对于异步代码不起作用,所以下面的代码永远不会执行结束。

const { VM } = require('vm2');
const vm = new VM({ timeout: 1000, sandbox: {}});
vm.run('new Promise(()=>{})');

即使希望通过重新定义Promise的方式来禁用Promise的话,还是一个可以绕过的

const { VM } = require('vm2');
const vm = new VM({ 
 timeout: 1000, sandbox: { Promise: function(){}}
});
vm.run('Promise = (async function(){})().constructor;new Promise(()=>{});');

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持三水点靠木。

Javascript 相关文章推荐
javascript中巧用“闭包”实现程序的暂停执行功能
Apr 04 Javascript
javascript中的107个基础知识收集整理 推荐
Mar 29 Javascript
两个select多选模式的选项相互移动(示例代码)
Jan 11 Javascript
js实现按Ctrl+Enter发送效果
Sep 18 Javascript
javascript获得当前的信息的一些常用命令
Feb 25 Javascript
JavaScript类型系统之基本数据类型与包装类型
Jan 06 Javascript
JS获取复选框的值,并传递到后台的实现方法
May 30 Javascript
微信小程序 五星评价功能的实现
Mar 09 Javascript
js遍历获取表格内数据的方法(必看)
Apr 06 Javascript
vue实现树形菜单效果
Mar 19 Javascript
5分钟快速看懂ES6中的反射与代理
Dec 19 Javascript
详解JavaScript数据类型和判断方法
Sep 04 Javascript
npm全局模块卸载及默认安装目录修改方法
May 15 #Javascript
WebPack配置vue多页面的技巧
May 15 #Javascript
修改npm全局安装模式的路径方法
May 15 #Javascript
修改node.js默认的npm安装目录实例
May 15 #Javascript
Vue中的scoped实现原理及穿透方法
May 15 #Javascript
vue-cli项目中使用Mockjs详解
May 14 #Javascript
vue使用自定义icon图标的方法
May 14 #Javascript
You might like
坏狼的PHP学习教程之第2天
2008/06/15 PHP
PHP 分页类(模仿google)-面试题目解答
2009/09/13 PHP
使用WAMP搭建PHP本地开发环境
2017/05/10 PHP
Ucren Virtual Desktop V2.0
2006/11/07 Javascript
SyntaxHighlighter代码加色使用方法
2008/09/07 Javascript
jQuery支持动态参数将函数绑定到事件上的方法
2015/03/17 Javascript
jQuery中(function($){})(jQuery)详解
2015/07/15 Javascript
基于JavaScript代码实现随机漂浮图片广告
2016/01/05 Javascript
JavaScript知识点总结之如何提高性能
2016/01/15 Javascript
jQuery学习笔记之回调函数
2016/08/15 Javascript
jQuery Ajax 实现在html页面实时显示用户登录状态
2016/12/30 Javascript
使用jQuery和ajax代替iframe的方法(详解)
2017/04/12 jQuery
vue轮播图插件vue-concise-slider的使用
2018/03/13 Javascript
React Router V4使用指南(精讲)
2018/09/17 Javascript
详解微信小程序缓存--缓存时效性
2019/05/02 Javascript
Vue3.x源码调试的实现方法
2019/10/13 Javascript
微信小程序跨页面传递data数据方法解析
2019/12/13 Javascript
Vue中fragment.js使用方法小结
2020/02/17 Javascript
[00:34]DOTA2上海特级锦标赛 Spirit战队宣传片
2016/03/04 DOTA
python计算最小优先级队列代码分享
2013/12/18 Python
Python实现将一个正整数分解质因数的方法分析
2017/12/14 Python
Python基于matplotlib实现绘制三维图形功能示例
2018/01/18 Python
python  logging日志打印过程解析
2019/10/22 Python
Pytorch 多块GPU的使用详解
2019/12/31 Python
css3的transform造成z-index无效解决方案
2014/12/04 HTML / CSS
澳大利亚首屈一指的在线购物目的地:Kogan.com
2017/02/02 全球购物
印度化妆品购物网站:Nykaa
2018/07/22 全球购物
屈臣氏乌克兰:Watsons UA
2019/10/29 全球购物
Sunglass Hut巴西网上商店:男女太阳镜
2020/10/04 全球购物
日本最大的彩色隐形眼镜销售网站:CharmColor
2020/09/09 全球购物
Jar包的作用是什么
2014/03/30 面试题
函授自我鉴定范文
2014/02/06 职场文书
干部对照检查材料范文
2014/08/26 职场文书
2014年发展党员工作总结
2014/11/12 职场文书
解决golang post文件时Content-Type出现的问题
2021/05/02 Golang
javascript函数式编程基础
2021/09/15 Javascript