В официальной документации узла упоминается, что модуль 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 не может указать контекст
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);
Поскольку доступ к локальной области невозможен, доступ возможен только к текущему глобальному объекту, поэтому приведенный выше код сообщит об ошибке, поскольку 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 и RunInnewcontext отличаются параметрами, переданными в контексте RunIncontext прошел объект контекста не пуст и должен быть по VM.createContext () обработанным, в противном случае это будет ошибка. Параметр контекста должен быть не работаетNewContext, и без прохождения процесса VM.createContext. RunInnewcontext и runincontext, потому что есть указанный контекст, чтобы не создавать глобальную загрязнение rounithiscontext (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().
Контроль тайм-аута и отлов ошибок
Виртуальная машина предоставляет механизм тайм-аута для выполнения кода.Указав параметр тайм-аута, вы можете запуститьInThisContext в качестве примера.
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, за ним фактически создается скрипт, как видно из предыдущего сообщения об ошибке. Далее мы будем использовать 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, узел добавил vm.Module в версии 9.6, чтобы включить отложенное выполнение.vm.Module в основном используется для поддержки модулей ES6, и его контекст уже связан при его создании.О vm.Module в настоящее время также требуется флаг в командной строке для включения поддержки
node --experimental-vm-module index.js
Безопасна ли виртуальная машина в качестве среды песочницы?
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 не работает для асинхронного кода, следующий код никогда не завершит выполнение.
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(()=>{});');
Суммировать
Виртуальная машина предоставляет изолированный способ выполнения ненадежного кода, но он не очень тщательный. Лучший способ выполнения ненадежного кода — «физическая изоляция», например док-контейнер.
использованная литература
https://nodejs.org/dist/latest-v10.x/docs/api/vm.html
https://60devs.com/executing-js-code-with-nodes-vm-module.html
https://odino.org/eval-no-more-understanding-vm-vm2-nodejs/
https://segmentfault.com/a/1190000014533283