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 statementat 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 definedat evalmachine.<anonymous>:1:1at 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:91return 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(()=>{});');

总结

vm提供了一种隔离的方式来执行不可信代码,但是并不是非常彻底,针对不可信代码最好的执行方式还是“物理隔离”,比如docker容器。

Node.js 沙箱环境相关推荐

  1. Node.js沙箱逃逸

    什么是沙箱(sandbox) 在计算机安全性方面,沙箱(沙盒.sanbox)是分离运行程序的安全机制,提供一个隔离环境以运行程序.通常情况下,在沙箱环境下运行的程序访问计算机资源会受到限制或者禁止,资 ...

  2. 【JS逆向】魔改Node JS补环境框架

    [JS逆向]魔改Node JS补环境框架 前言 魔改Node Js补环境框架 内置模块wanfeng 内置对象Utils node框架使用说明 js框架简介 注意事项 警告 赞助 结束语 前言 由于J ...

  3. 计算机系统的搭建步骤,电脑搭建Node.js开发环境的操作教程[多图]

    电脑如何搭建Node.js开发环境?近日有用户询问怎么在Win7系统电脑上搭建Node.js开发环境,今天教程之家就给大家分享Node.js开发环境的搭建教程. 操作步骤: 1.下载Node.js官方 ...

  4. 手把手教你学node之搭建node.js开发环境

    搭建node.js开发环境 本文只针对在Linux或者Mac下面.至于使用 Windows 并坚持玩新技术的同学,我坚信他们一定有着过人的.甚至是不可告人的兼容性 bug 处理能力,所以这部分同学麻烦 ...

  5. Node.js的环境搭建方法和 npm 的使用方法

    Node.js的环境搭建方法和 npm 的使用方法 Node.js 环境的搭建 Node.js 介绍 Node.js 下载 配置Node.js node.js 测试 npm(包管理器)使用方法 npm ...

  6. 基于node.js开发环境下创建及开发vue.js项目的环境配置骤

    基于node.js开发环境下创建开发vue.js项目的环境配置骤如下: 步骤一:安装node.js,安装完后运行node -v命令检安装node的查版本,判断是否安装成功.Npm是node.js包管理 ...

  7. 在windows环境下基于sublime text3的node.js开发环境搭建

    摘自:http://blog.sina.com.cn/s/blog_5a6efa330102vcla.html  (略有修改) 首先安装sublime text3,百度一堆,自己找吧.理论上subli ...

  8. node.js开发环境配置

    node.js是什么 Node.js 是一个基于 Chrome V8 引擎的 JavaScript 运行环境.Node.js 使用了一个事件驱动.非阻塞式 I/O 的模型,使其轻量又高效.Node.j ...

  9. Node.js 安装---环境配置---输出Hello World !

    目   录 [一].推荐学习资源 [二].安装Node环境 (2.1)查看当前Node环境的版本号 (2.2)下载软件:https://nodejs.org/en/ [Node.js 官网] (2.3 ...

最新文章

  1. (0056)iOS开发之深拷贝与浅拷贝
  2. HealthKit开发快速入门教程之HealthKit框架体系创建健康AppID
  3. Cissp-【第6章 安全评估与测试】-2021-3-15(661页-706页)
  4. 同样是程序员,优秀的程序员能够月入5万甚至50万!,一般的程序员却只能月入5千甚至更低?那么他们差别在哪里呢?
  5. 贴几张最新最精彩的电影海报
  6. 怎么升级计算机硬盘,研究僧 篇一:记一次老电脑的升级之路
  7. 质因数分解(0)P2012_1
  8. eclipse+Maven安装和配置
  9. iphone编辑过的录音怎么还原_ios录音误剪怎么恢复
  10. 综合支撑【恶灵附身 Psycho Break】的世界观的概念艺术
  11. 手把手教你如何免费把多张图片合并为PDF文件,一看就懂。
  12. Google SEO入门教程,技术 SEO 简介
  13. ubuntu 16 xenial EKL安装
  14. 看纸箱设备厂家如何定义包装纸箱的
  15. 阿里云物联网Iot设备上下线状态数据流转的设置
  16. 数据库——第二章关系运算题整理
  17. 使用计算机辐射最大,台式电脑哪里辐射最大
  18. 01postman学习笔记记录
  19. 图集谷-写真集-爬虫-1.0
  20. 前端打包利器,webpack工具,app打包工具

热门文章

  1. EasyRecovery Photo16数据恢复软件免费版下载或使用方法及安装激活教程
  2. 西电计算机学院戚玉涛,西安电子科技大学计算机学院硕导介绍:戚玉涛
  3. 安卓手机更新过程手机乱码_安卓手机怎么换字体与改回原字体乱码解决办法
  4. 手把手带你做强化学习实验--敲级详细
  5. ARM 触摸屏 TouchScreen
  6. 10进制数转换成16进制
  7. lisp CAD二次开发 宗地线自动编号
  8. 系统检测效果html,系统检测(MonitorTest)
  9. win10系统打开文件出现安全警告窗口怎么关闭?
  10. 2015081501 - 仙剑奇侠传四的纪念