流出节点沙盒
这是原始的一叉 vm2 数据 复发. 为解决CVE问题,已经进行了修改.
vm2是一个沙盒,可以使用白列节点的内置模块运行不可信代码.
特征
- 在单个过程中安全运行未信任的代码, 并肩运行代码
- 全面控制沙盒控制台输出
- 沙盒对过程方法的使用有限
- 可以从沙盒中需要模块(内建和外建)
- 您可以限制访问某些( 或所有) 内置模块
- 您可以在沙盒之间安全地调用方法并交换数据和调用
- 不受一切已知攻击方法的影响
- 传输支持
怎么会这样
- 它使用内部的VM模块来创建安全上下文.
- 用这个 代理 防止逃离沙盒.
- 它超越了控制模块访问所需的内置.
节点的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
安装
重要性: VM2需要Node.js 6或更新.
npm install vm2
快速示例
const {VM} = require('vm2');
const vm = new VM();
vm.run(`process.exit()`); // TypeError: process.exit is not a function
const {NodeVM} = require('vm2');
const vm = new NodeVM({
require: {
external: true,
root: './'
}
});
vm.run(`
var request = require('request');
request('http://www.google.com', function (error, response, body) {
console.error(error);
if (!error && response.statusCode == 200) {
console.log(body); // Show the HTML for the Google homepage.
}
});
`, 'vm.js');
文档
志愿人员
VM 是一个简单的沙盒,可以同步运行无信任的代码 require 特性。 只有 JavaScript 内置对象和节点对象 Buffer 备有。 排期函数( E)setInterval, (中文(简体) ), setTimeout 财务报告和审定财务报表 setImmediate) 默认不可用.
选项 :
timeout- 脚本超时数以毫秒计. 警告: 您可能想要一起使用此选项allowAsync=false。此外,在从沙盒返回的对象上操作可以运行任意代码并绕过超时. 如果返回的物体是原始的,应该进行测试typeof并完全丢弃它(使用此对象进行记录或创建错误消息也可能再次运行任意代码) .sandbox- VM的全球对象.compiler- 说吧javascript(违约)或coffeescript或自定义编译器函数。 如果编译器设定为coffeescript。 。 。 .eval- 如果设置为false任何呼吁eval或函数构造器( e)Function, (中文(简体) ),GeneratorFunction)将丢出一个EvalError(默认 :true) (中文(简体) ).wasm- 如果设置为false任何试图编译WebAssembly模块的尝试都会投出一个WebAssembly.CompileError(默认 :true) (中文(简体) ).allowAsync- 如果设置为false试图使用async将丢出一个VMError(默认 :true) (中文(简体) ).
重要性: 超时只在运行的同步代码上有效 run超时 没有 vM返回的任何方法的工作。 有些时候超时不起作用 244号。 。 。 .
const {VM} = require('vm2');
const vm = new VM({
timeout: 1000,
allowAsync: false,
sandbox: {}
});
vm.run('process.exit()'); // throws ReferenceError: process is not defined
您也可以从 VM 获取值 .
let number = vm.run('1337'); // returns 1337
人口贩运: 更多用法示例见测试.
节点VM
与 VM, (中文(简体) ), NodeVM 允许您以与常规节点上下文相同的方式要求模块.
选项 :
console- 说吧inherit启用控制台,redirect转向事件,off以禁用控制台( 默认 ):inherit) (中文(简体) ).sandbox- VM的全球对象.compiler- 说吧javascript(违约)或coffeescript或自定义编译器功能(即接收代码,且是文件路径). 如果编译器设定为coffeescript。 。 。 .eval- 如果设置为false任何呼吁eval或函数构造器( e)Function, (中文(简体) ),GeneratorFunction)将丢出一个EvalError(默认 :true) (中文(简体) ).wasm- 如果设置为false任何试图编译WebAssembly模块的尝试都会投出一个WebAssembly.CompileError(默认 :true) (中文(简体) ).sourceExtensions- 文件扩展组,作为源代码处理(默认:['js']) (中文(简体) ).require- 说吧true,用于启用require方法( 默认 ):false) (中文(简体) ).require.external- 价值观可以是true,允许的外部模块数组,或对象(默认:false) (中文(简体) ). 所有路径匹配/node_modules/${any_allowed_external_module}/(?!/node_modules/)允许要求.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在沙盒中装入、编译和需要模块.callback(moduleFilename, ext)以动态方式选择每个模块的背景。 默认为沙盒( sandbox) 。 除外events,内置模块总是在主机中需要并被代理入沙盒.require.import- 启动时将装入NodeVM的一系列模块.require.resolve- 在传统的节点搜索路径中找不到模块时,需要额外的搜索功能.require.customRequire- 使用代替require从主机装入模块的函数.require.strict- 说吧false以不强制要求加载的模块采用严格模式(默认:true) (中文(简体) ).require.fs- 自定义文件系统执行.nesting- 说吧 警告:允许这是安全风险,因为脚本可以创建一个可以要求任何主机模块的节点VM.true启用 VM 嵌入( 默认 ) :false) (中文(简体) ).wrapper- 说吧commonjs(默认)将脚本包入普通JS包装器,none以获取脚本返回的值.argv- 被传递到process.argv。 。 。 .env- 传递给process.env。 。 。 .strict- 说吧true以严格模式装入模块(默认值 :false) (中文(简体) ).
重要性: 超时对NodeVM无效,所以不能豁免 while (true) {} 或类似的邪恶.
记得:你允许的模块越多,你的沙盒就越脆弱.
const {NodeVM} = require('vm2');
const vm = new NodeVM({
console: 'inherit',
sandbox: {},
require: {
external: true,
builtin: ['fs', 'path'],
root: './',
mock: {
fs: {
readFileSync: () => 'Nice try!'
}
}
}
});
// Sync
let functionInSandbox = vm.run('module.exports = function(who) { console.log("hello "+ who); }');
functionInSandbox('world');
// Async
let functionWithCallbackInSandbox = vm.run('module.exports = function(who, callback) { callback("hello "+ who); }');
functionWithCallbackInSandbox('world', (greeting) => {
console.log(greeting);
});
何时 wrapper 设置为 none, (中文(简体) ), NodeVM 行为更像 VM 用于同步代码.
assert.ok(vm.run('return true') === true);
人口贩运: 更多用法示例见测试.
通过相对路径加载模块
要通过相对路径加载模块,就必须通过您运行的脚本的全部路径,作为 vm 的第二个参数 run 如果脚本是字符串,则使用方法。 文件名会显示在脚本生成的任何堆栈痕迹中 .
vm.run('require("foobar")', '/data/myvmscript.js');
如果您正在运行的脚本是 VMScript, 路径会在 VMScript 构建器中给出 .
const script = new VMScript('require("foobar")', {filename: '/data/myvmscript.js'});
vm.run(script);
解析器
解析器可以通过 makeResolverFromLegacyOptions 用于多个 NodeVM 允许共享已编译的模块代码的实例, 可能加快加载时间 。 第一个例子是 NodeVM 可用 makeResolverFromLegacyOptions 如下所示.
const resolver = makeResolverFromLegacyOptions({
external: true,
builtin: ['fs', 'path'],
root: './',
mock: {
fs: {
readFileSync: () => 'Nice try!'
}
}
});
const vm = new NodeVM({
console: 'inherit',
sandbox: {},
require: resolver
});
VMS 脚本
可以通过使用预编译脚本来提高性能. 预编译的VMScript可以多次运行. 需要注意的是,代码与任何VM(文本)无关,而是在每次运行前,仅为该运行而绑定.
const {VM, VMScript} = require('vm2');
const vm = new VM();
const script = new VMScript('Math.random()');
console.log(vm.run(script));
console.log(vm.run(script));
两者都有用 VM 财务报告和审定财务报表 NodeVM。 。 。 .
const {NodeVM, VMScript} = require('vm2');
const vm = new NodeVM();
const script = new VMScript('module.exports = Math.random()');
console.log(vm.run(script));
console.log(vm.run(script));
代码在第一次运行时自动被编译. 随时可以编译代码 script.compile()。一旦编译出代码,该方法就没有效果.
处理错误
代码编译错误和同步代码执行错误可以通过 try-catch。同步代码执行中的错误可以通过附加处理 uncaughtException 事件处理器到节点 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);
});
调试沙盒代码
您可以调试或检查在沙盒中运行的代码, 仿佛它在正常过程中运行.
- 您可以使用断点( 需要您指定脚本文件名称)
- 你用得着
debugger关键词. - 可以使用一步入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入出出入入入入出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出.
示例
/tmp/main.js: (中文(简体) )
const {VM, VMScript} = require('.');
const fs = require('fs');
const file = `${__dirname}/sandbox.js`;
// By providing a file name as second argument you enable breakpoints
const script = new VMScript(fs.readFileSync(file), file);
new VM().run(script);
/tmp/sandbox.js (英语)
const foo = 'ahoj';
// The debugger keyword works just fine everywhere.
// Even without specifying a file name to the VMScript object.
debugger;
只读对象( 实验)
为了防止沙盒脚本从代理对象中添加、更改或删除属性,可以使用 freeze 使对象只读的方法。 这只在VM内部有效. 被冻结的物体受到严重影响。 原始类型不能被冻结.
不使用示例 freeze数字 :
const util = {
add: (a, b) => a + b
}
const vm = new VM({
sandbox: {util}
});
vm.run('util.add = (a, b) => a - b');
console.log(util.add(1, 1)); // returns 0
使用示例 freeze数字 :
const vm = new VM(); // Objects specified in the sandbox cannot be frozen.
vm.freeze(util, 'util'); // Second argument adds object to global.
vm.run('util.add = (a, b) => a - b'); // Fails silently when not in strict mode.
console.log(util.add(1, 1)); // returns 2
关键是: 无法冻结已经代理VM的物体.
受保护物体(实验性)
与 freeze,这种方法允许沙盒脚本在对象上添加,更改,或删除属性,只有一个例外——无法附加函数. 因此,沙盒脚本无法修改方法如 toJSON, (中文(简体) ), toString 或者说 inspect。 。 。 .
关键是: 无法保护已经代理 VM 的物体 .
跨框关系
const assert = require('assert');
const {VM} = require('vm2');
const sandbox = {
object: new Object(),
func: new Function(),
buffer: new Buffer([0x01, 0x05])
}
const vm = new VM({sandbox});
assert.ok(vm.run(`object`) === sandbox.object);
assert.ok(vm.run(`object instanceof Object`));
assert.ok(vm.run(`object`) instanceof Object);
assert.ok(vm.run(`object.__proto__ === Object.prototype`));
assert.ok(vm.run(`object`).__proto__ === Object.prototype);
assert.ok(vm.run(`func`) === sandbox.func);
assert.ok(vm.run(`func instanceof Function`));
assert.ok(vm.run(`func`) instanceof Function);
assert.ok(vm.run(`func.__proto__ === Function.prototype`));
assert.ok(vm.run(`func`).__proto__ === Function.prototype);
assert.ok(vm.run(`new func() instanceof func`));
assert.ok(vm.run(`new func()`) instanceof sandbox.func);
assert.ok(vm.run(`new func().__proto__ === func.prototype`));
assert.ok(vm.run(`new func()`).__proto__ === sandbox.func.prototype);
assert.ok(vm.run(`buffer`) === sandbox.buffer);
assert.ok(vm.run(`buffer instanceof Buffer`));
assert.ok(vm.run(`buffer`) instanceof Buffer);
assert.ok(vm.run(`buffer.__proto__ === Buffer.prototype`));
assert.ok(vm.run(`buffer`).__proto__ === Buffer.prototype);
assert.ok(vm.run(`buffer.slice(0, 1) instanceof Buffer`));
assert.ok(vm.run(`buffer.slice(0, 1)`) instanceof Buffer);
国 际
在命令行中使用 vm2 之前, 在全球安装它 npm install vm2 -g。 。 。 .
vm2 ./script.js
已知问题
- 有一些已知的安全问题来规避沙盒.
- 无法定义扩展代位类的类. 这包括使用在
Object.create。 。 。 . - 直接Eval不起作用.
- 日志沙盒阵列将重复属性中的数组部分.
- 源代码转换可以为一个函数产生不同的源字符串.
- 从沙盒内部可以崩溃节点过程.
部署
- 更新
package.json版本编号 - 提交更改
- 运行
npm publish