Каковы сценарии динамического выполнения сценариев?
В некоторых приложениях мы хотим предоставить пользователям возможность вставлять пользовательскую логику, например, в Microsoft Office.VBA
, как в некоторых играхlua
Script, «сценарий масляной обезьяны» FireFox, позволяет пользователям использовать свое воображение, чтобы делать некоторые забавные и полезные вещи в пределах контролируемого объема и полномочий, расширять свои возможности и удовлетворять персонализированные потребности пользователей.
Большинство из них являются клиентскими программами, и в некоторых онлайн-системах и продуктах часто предъявляются аналогичные требования.На самом деле, многие онлайн-приложения также предоставляют возможность настраивать сценарии, такие как Google Docs.Apps Script
, что позволяет использоватьJavaScript
Делайте очень полезные вещи, например запускайте код в ответ на события открытия документа или события изменения ячейки, создавайте собственные функции электронной таблицы для формул и многое другое.
В отличие от клиентских приложений, работающих «на компьютере пользователя», пользовательские сценарии обычно влияют только на самого пользователя.Для онлайн-приложений или служб некоторые ситуации становятся более важными, например, «безопасность», пользовательский «пользовательский сценарий» должен быть строго ограничен. и изолированный, то есть он не может повлиять на хост-программу, а также не может повлиять на других пользователей.
Safeify — это модуль для приложений Nodejs, позволяющий безопасно выполнять определенные пользователем ненадежные сценарии.
Как безопасно выполнять динамические скрипты?
Давайте сначала посмотрим, как кусок кода обычно может выполняться динамически в программе на JavaScript? такие как знаменитыйeval
eval('1+2')
Приведенный выше код выполняется успешно без каких-либо проблем.eval
Это функциональный атрибут глобального объекта. Исполняемый код имеет те же разрешения, что и другой обычный код в приложении. Он может обращаться к локальным переменным в «контексте выполнения» и ко всем «глобальным переменным». В этом сценарии это очень опасная функция.
посмотри сноваFuncton
,пройти черезFunction
Конструктор, мы можем динамически создать функцию, а затем выполнить ее
const sum = new Function('m', 'n', 'return m + n');
console.log(sum(1, 2));
Он также выполняется плавно.Функции, сгенерированные с помощью конструктора функций, не создают замыканий в том контексте, в котором они были созданы, а обычно создаются в глобальной области видимости. При запуске функции она может получить доступ только к своим собственным локальным переменным и глобальным переменным и не может получить доступ к области контекста, созданной вызываемым конструктором функции. Как стоящий на земле и стоящий на тонком листе бумаги, в этой сцене почти нет разницы.
Включение новых функций ES6Proxy
быть в большей безопасности
function evalute(code,sandbox) {
sandbox = sandbox || Object.create(null);
const fn = new Function('sandbox', `with(sandbox){return (${code})}`);
const proxy = new Proxy(sandbox, {
has(target, key) {
// 让动态执行的代码认为属性已存在
return true;
}
});
return fn(proxy);
}
evalute('1+2') // 3
evalute('console.log(1)') // Cannot read property 'log' of undefined
мы знаем, что неважноeval
ещеfunction
, область будет искаться слой за слоем во время выполнения, если не будет найдена, то до тех пор, покаglobal
, затем используйтеProxy
Принцип заключается в том, чтобы исполняемый кодsandobx
Для достижения цели «предотвратить побег».
В браузере вы также можете использовать iframe для создания более безопасной изолированной среды.Эта статья посвящена Node.js и не будет подробно обсуждать его здесь.
В Node.js есть другие варианты?
Может быть, вы уже думали об этом, прежде чем вы это увиделиVM
, который является встроенным модулем, предоставляемым по умолчанию в Node.js,VM
Модули предоставляют набор API для компиляции и запуска кода в среде виртуальной машины V8. Код JavaScript можно скомпилировать и запустить сразу или скомпилировать, сохранить и затем запустить.
const vm = require('vm');
const script = new vm.Script('m + n');
const sandbox = { m: 1, n: 2 };
const context = new vm.createContext(sandbox);
script.runInContext(context);
Выполните приведенный выше код, чтобы получить результат3
, в то же время черезvm.Script
Вы также можете указать, что код выполнялся в течение «максимального количества миллисекунд», после чего выполнение будет прекращено и будет выброшено исключение
try {
const script = new vm.Script('while(true){}',{ timeout: 50 });
....
} catch (err){
//打印超时的 log
console.log(err.message);
}
Выполнение вышеприведенного скрипта завершится ошибкой, будет обнаружено время ожидания и будет выдано исключение, а затемTry Cache
Захватите и распечатайте лог, но при этом следует отметить, чтоvm.Script
изtimeout
Опция «действительна только для синхронной генерации» не включает время, когда это асинхронный вызов, например
const script = new vm.Script('setTimeout(()=>{},2000)',{ timeout: 50 });
....
Приведенный выше код не генерирует исключение через 50 мс, потому что код выше 50 мс должен выполняться синхронно, иsetTimeout
Используемое время не учитывается, т.vm
У модулей нет возможности ограничить время выполнения непосредственно асинхронным кодом. Также мы не можем дополнительно передатьtimer
проверить тайм-аут, потому что нет возможности прервать после проверки работающей виртуальной машины.
Кроме того, в Node.js черезvm.runInContext
Кажется, что это изолирует среду выполнения кода, но на самом деле от нее легко «убежать».
const vm = require('vm');
const sandbox = {};
const script = new vm.Script('this.constructor.constructor("return process")().exit()');
const context = vm.createContext(sandbox);
script.runInContext(context);
Выполните приведенный выше код, хост-программа немедленно «выйдет»,sandbox
вVM
созданные в среде, отличной отVM
код вthis
также указывает наsandbox
,Так
//this.constructor 就是外所的 Object 构建函数
const ObjConstructor = this.constructor;
//ObjConstructor 的 constructor 就是外包的 Function
const Function = ObjConstructor.constructor;
//创建一个函数,并执行它,返回全局 process 全局对象
const process = (new Function('return process'))();
//退出当前进程
process.exit();
Никто не хочет использовать скрипт для уничтожения приложения. На самом деле нужно сделать больше, чем просто выйти из программы.
Есть простой способ избежатьthis.constructor
получатьprocess
,следующим образом:
const vm = require('vm');
//创建一外无 proto 的空白对象作为 sandbox
const sandbox = Object.create(null);
const script = new vm.Script('...');
const context = vm.createContext(sandbox);
script.runInContext(context);
Но риски все равно есть: из-за динамических характеристик самого JavaScript трудно защититься от всех видов черной магии. На самом деле, в официальной документации Node.js также упоминается «Не ставьтеVM
Действуйте как безопасная песочница для выполнения произвольного ненадежного кода».
Какие модули сообщества выполняют дальнейшую работу?
В сообществе есть несколько модулей с открытым исходным кодом для запуска ненадежного кода, напримерsandbox
,vm2
,jailed
Ждать. Сравнениеvm2
Во всех аспектах было проделано больше работы по обеспечению безопасности, что является относительно безопасным.
отvm2
официальныйREADME
Как видите, он основан на встроенном модуле виртуальной машины Node.js для создания базовой среды песочницы, а затем одновременно использует представленную выше модель ES6.Proxy
Методы предотвращения выхода сценариев песочницы.
Попробуйте с тем же тестовым кодомvm2
const { VM } = require('vm2');
new VM().run('this.constructor.constructor("return process")().exit()');
Как и в приведенном выше коде, нет успешного окончания записи, официальный Reame VM2, «VM2 — это песочница, которую можно выполнить в Node.js».
Однако на самом деле мы все еще можем делать некоторые «плохие» вещи, такие как:
const { VM } = require('vm2');
const vm = new VM({ timeout: 1000, sandbox: {}});
vm.run('new Promise(()=>{})');
Приведенный выше код никогда не закончится, как и встроенные модули Node.js в vm2.timeout
Недействительно для асинхронных операций. в то же время,vm2
Не может быть и дополнительногоtimer
Иди проверь таймаут, потому что у него тоже нет возможности убить запущенную вм. Это постепенно истощит ресурсы сервера и приведет к зависанию вашего приложения.
Тогда, может быть, вам интересно, можем ли мы быть там?sandbox
ПодделкаPromise
А как насчет запрета промисов? Ответ заключается в том, чтобы предоставить "ложь"Promise
, но нет возможности завершить банPromise
,Например
const { VM } = require('vm2');
const vm = new VM({
timeout: 1000, sandbox: { Promise: function(){}}
});
vm.run('Promise = (async function(){})().constructor;new Promise(()=>{});');
можно увидеть по линииPromise = (async function(){})().constructor
вы можете легко получить его сноваPromise
. На другом уровне, и, возможно, иногда мы хотим, чтобы пользовательские сценарии поддерживали асинхронную обработку.
Как построить более безопасную песочницу?
В ходе вышеуказанного исследования мы не нашли идеального решения для создания безопасной и изолированной песочницы в Node.js. Среди них vm2 проделал большую обработку, что является относительно безопасным решением, но проблемы также очевидны, например, проблема в том, что асинхронный не может проверить тайм-аут, и проблема в том, что хост-программа находится в том же процессе.
Без изоляции процессов санбокс, созданный ВМ, выглядит так
Итак, можем ли мы попытаться изолировать ненадежный код и выполнить его в отдельном процессе через модуль vm2? Затем, когда время выполнения истекает, изолированный процесс непосредственно уничтожается, но здесь мы должны учитывать следующие проблемы.
Унифицированное планирование и управление процессами песочницы через пулы процессов
Если вы приходите к задаче на выполнение, создаете процесс и уничтожаете его после использования, накладные расходы только на обработку процесса уже немного больше, и вы не можете без ограничений открывать новые процессы и размещать приложения для захвата ресурсов, то вам нужно построить пул процессов, все. Когда задача прибудет, она создастScript
например, сначала введитеpending
очереди, а затем напрямуюscript
примерdefer
Объект возвращается, и вызывающая сторона можетawait
результат выполнения, затем поsandbox master
Выполнение запланировано в соответствии с программой простоя процесса проекта, и мастерscript
информация о внедрении, в том числе важнаяScriptId
, отправленный бездействующему рабочему процессу. После того, как рабочий процесс завершит выполнение, он отправит «результат + информация о сценарии» обратно главному компьютеру. Мастер определяет, какой сценарий завершил выполнение, с помощью ScriptId, и результатом является результат.resolve
или отказаться от обработки.
Таким образом, «пул процессов» может снизить «накладные расходы на создание и уничтожение процессов» и гарантировать, что ресурсы хоста не будут вытеснены.В то же время, когда время асинхронной операции истекает, процесс разработки может быть остановлен напрямую. При этом мастер обнаружит, что при зависании процесса проекта будет немедленно создан процесс замены.
Обработанные данные и результаты, а также методы, представленные в песочнице
Для связи между процессами требуется «динамический код» для обработки данных, которые могут быть напрямую сериализованы и отправлены в изолированный процесс «песочницы» через IPC.Результаты выполнения также сериализуются и передаются через IPC.
Среди них, если идея состоит в том, чтобы выставить метод в песочницу, потому что он не находится в процессе, неудобно передавать ссылку на схему в песочницу. Мы можем передать метод хоста рабочему песочнице для обработки, преобразовать его в «объект описания», включая информацию о методе, который позволяет песочнице вызывать, а затем отправить информацию рабочему процессу, как и другие данные, и рабочий процесс получает ее. , После получения данных идентифицируйте «объект описания метода», а затем создайте прокси-метод для объекта песочницы в рабочем процессе.Прокси-метод также взаимодействует с мастером через IPC.
Квоты ЦП и памяти для изолированных процессов
На платформе Linux CGoups используются для ограничения общих ресурсов ЦП и памяти процесса песочницы.Cgroups — это аббревиатура Control Groups.Механизм физических ресурсов (таких как: ЦП, память, ввод-вывод и т. д.). Первоначально предложенный инженерами Google, позже он был интегрирован в ядро Linux. Cgroups также является методом управления ресурсами, используемым LXC для достижения виртуализации.Можно сказать, что LXC не существует без CGroups.
В итоге мы построили примерно такую «песочницу».
Вам не кажется, что с этим так сложно иметь дело? Но теперь у нас есть более безопасная среда песочницы, и они обрабатываются. Автор написал его на основе TypeScript и инкапсулировал как самостоятельный модуль.Safeify
.
По сравнению со встроенной виртуальной машиной и несколькими распространенными модулями песочницы Safeify имеет следующие характеристики:
- Создайте выделенный пул процессов для динамического кода, который будет выполняться отдельно от основного приложения, и выполняйте его в другом процессе.
- Поддерживает настройку максимального количества процессов в пуле процессов песочницы.
- Поддерживает ограничение максимального времени выполнения синхронного кода, а также поддерживает ограничение времени выполнения, включая асинхронный код.
- Поддержка ограничения общей квоты ресурсов ЦП (десятичной) пула процессов песочницы.
- Поддержка ограничения общего максимального предела памяти пула процессов песочницы (единица m)
GitHub: https://github.com/Houfeng/safeify, добро пожаловать Star & Issues
Наконец, краткое введение в то, как Safeify используется и устанавливается с помощью следующей команды
npm i safeify --save
Относительно просто использовать в приложении следующий код (похожий на TypeScript)
import { Safeify } from './Safeify';
const safeVm = new Safeify({
timeout: 50, //超时时间,默认 50ms
asyncTimeout: 500, //包含异步操作的超时时间,默认 500ms
quantity: 4, //沙箱进程数量,默认同 CPU 核数
memoryQuota: 500, //沙箱最大能使用的内存(单位 m),默认 500m
cpuQuota: 0.5, //沙箱的 cpu 资源配额(百分比),默认 50%
});
const context = {
a: 1,
b: 2,
add(a, b) {
return a + b;
}
};
const rs = await safeVm.run(`return add(a,b)`, context);
console.log('result',rs);
По поводу безопасности-нет самого безопасного,есть только безопаснее.В проекте использовался Safeify,но функция пользовательских скриптов только для интранет-пользователей.Много сценариев динамического исполнения кода,которых можно избежать.При открытии или очень нужно чтобы обеспечить эту функцию, я надеюсь, что эта статья или Safeify может помочь вам.
-- end --