«Если рабочий хочет хорошо выполнять свою работу, он должен сначала заточить свои инструменты» — Конфуций, «Аналитики Конфуция. Лу Лингун»
титульная страница > программирование > Запуск ненадежного кода JavaScript

Запуск ненадежного кода JavaScript

Опубликовано 29 июля 2024 г.
Просматривать:111

Running Untrusted JavaScript Code

ВАЖНО: Речь идет только о запуске кода JavaScript и TypeScript. При этом написание также может быть направлением для запуска другого кода на других языках.

Разрешение пользователям выполнять свой код в вашем приложении открывает мир настроек и функциональности, но при этом подвергает вашу платформу серьезным угрозам безопасности.

Учитывая, что это пользовательский код, ожидается все, от остановки серверов (это могут быть бесконечные циклы) до кражи конфиденциальной информации.

В этой статье будут рассмотрены различные стратегии по снижению риска запуска пользовательского кода, включая Web Workers, статический анализ кода и многое другое…

Вы должны заботиться

Существует множество сценариев, в которых вам необходимо запускать предоставленный пользователем код: от сред совместной разработки, таких как CodeSandbox и StackBiltz, до настраиваемых платформ API, таких как Январь. Даже игровые площадки с кодом подвержены рискам.

А именно, два существенных преимущества безопасного запуска предоставленного пользователем кода:

  1. Завоевание доверия пользователя: даже если пользователь заслуживает доверия, он может выполнить код, скопированный у других заведомо плохих людей.
  2. Защитите свою среду: последнее, что вам нужно, — это фрагмент кода, останавливающий ваш сервер. Подумайте, пока (правда) {}

Дайте определение «конфиденциальной информации».

Запуск пользовательского кода не представляет опасности, пока вы не опасаетесь, что это может привести к краже некоторых данных. Какие бы данные вас ни беспокоили, они будут считаться конфиденциальной информацией. Например, в большинстве случаев JWT является конфиденциальной информацией (возможно, при использовании в качестве механизма аутентификации)

Что может пойти не так

Учитывайте потенциальные риски, связанные с тем, что JWT хранится в файлах cookie, отправляемых с каждым запросом. Пользователь может случайно запустить запрос, который отправит JWT на вредоносный сервер, и...

  • Межсайтовый скриптинг (XSS).
  • Атаки типа «отказ в обслуживании» (DoS).
  • Кража данных. Без надлежащих мер безопасности эти угрозы могут поставить под угрозу целостность и производительность вашего приложения.

Методы

Злой Эвал

Самый простой из всех, но самый рискованный.

eval('console.log("I am dangerous!")');

Когда вы запускаете этот код, оно записывает это сообщение. По сути, eval — это JS-интерпретатор, способный получать доступ к глобальной/оконной области.

const res = await eval('fetch(`https://jsonplaceholder.typicode.com/users`)');
const users = await res.json();

Этот код использует выборку, определенную в глобальной области видимости. Интерпретатор об этом не знает, но поскольку eval имеет доступ к окну, он знает. Это означает, что запуск оценки в браузере отличается от его запуска в серверной или рабочей среде.

eval(`document.body`);

Как насчет этого...

eval(`while (true) {}`);

Этот код остановит вкладку браузера. Вы можете спросить, почему пользователь сделал это с собой. Ну, возможно, они копируют код из Интернета. Вот почему предпочтительнее выполнять статический анализ с/или ограничивать время выполнения.

Возможно, вы захотите проверить документацию MDN об eval

Выполнение временного интервала можно выполнить, запустив код в веб-воркере и используя setTimeout для ограничения времени выполнения.

async function timebox(code, timeout = 5000) {
  const worker = new Worker('user-runner-worker.js');
  worker.postMessage(code);

  const timerId = setTimeout(() => {
    worker.terminate();
    reject(new Error('Code execution timed out'));
  }, timeout);

  return new Promise((resolve, reject) => {
    worker.onmessage = event => {
      clearTimeout(timerId);
      resolve(event.data);
    };
    worker.onerror = error => {
      clearTimeout(timerId);
      reject(error);
    };
  });
}

await timebox('while (true) {}');

Конструктор функций

Это похоже на eval, но немного безопаснее, поскольку не позволяет получить доступ к окружающей области.

const userFunction = new Function('param', 'console.log(param);');
userFunction(2);

Этот код зарегистрирует 2.

Примечание: Второй аргумент — это тело функции.

Конструктор функции не может получить доступ к окружающей области, поэтому следующий код выдаст ошибку.

function fnConstructorCannotUseMyScope() {
  let localVar = 'local value';
  const userFunction = new Function('return localVar');
  return userFunction();
}

Но он может получить доступ к глобальной области видимости, поэтому приведенный выше пример работает.

Вебворкер

Вы можете запустить «Конструктор функций и eval» на WebWorker, что немного безопаснее из-за отсутствия доступа к DOM.

Чтобы ввести дополнительные ограничения, рассмотрите возможность запрета использования глобальных объектов, таких как fetch, XMLHttpRequest, sendBeacon. Прочтите эту статью о том, как это можно сделать.

Изолированная виртуальная машина

Isolated-VM — это библиотека, которая позволяет запускать код на отдельной виртуальной машине (интерфейс Isolate v8)

import ivm from 'isolated-vm';

const code = `count  = 5;`;

const isolate = new ivm.Isolate({ memoryLimit: 32 /* MB */ });
const script = isolate.compileScriptSync(code);
const context = isolate.createContextSync();

const jail = context.global;
jail.setSync('log', console.log);

context.evalSync('log("hello world")');

Этот код зарегистрирует hello world

Веб-сборка

Это интересный вариант, поскольку он предоставляет изолированную среду для запуска кода. Единственное предостережение: вам нужна среда с привязками Javascript. Однако этому способствует интересный проект Extism. Возможно, вы захотите следовать их руководству.

Что интересно, так это то, что вы будете использовать eval для запуска кода, но, учитывая природу WebAssembly, DOM, сеть, файловая система и доступ к хост-среде невозможны (хотя они могут различаться в зависимости от среда выполнения Wasm).

function evaluate() {
  const { code, input } = JSON.parse(Host.inputString());
  const func = eval(code);
  const result = func(input).toString();
  Host.outputString(result);
}

module.exports = { evaluate };

Сначала вам придется скомпилировать приведенный выше код с помощью Extism, который выведет файл Wasm, который можно запустить в среде, в которой есть среда выполнения Wasm (браузер или node.js).

const message = {
  input: '1,2,3,4,5',
  code: `
        const sum = (str) => str
          .split(',')
          .reduce((acc, curr) => acc   parseInt(curr), 0);
        module.exports = sum;
`,
};

// continue running the wasm file

Докер

Сейчас мы переходим на серверную часть. Docker — отличный вариант для запуска кода изолированно от хост-компьютера. (Остерегайтесь побега из контейнера)

Вы можете использовать dockerode для запуска кода в контейнере.

import Docker from 'dockerode';
const docker = new Docker();

const code = `console.log("hello world")`;
const container = await docker.createContainer({
  Image: 'node:lts',
  Cmd: ['node', '-e', code],
  User: 'node',
  WorkingDir: '/app',
  AttachStdout: true,
  AttachStderr: true,
  OpenStdin: false,
  AttachStdin: false,
  Tty: true,
  NetworkDisabled: true,
  HostConfig: {
    AutoRemove: true,
    ReadonlyPaths: ['/'],
    ReadonlyRootfs: true,
    CapDrop: ['ALL'],
    Memory: 8 * 1024 * 1024,
    SecurityOpt: ['no-new-privileges'],
  },
});

Имейте в виду, что вам необходимо убедиться, что на сервере установлен и работает докер. Я бы порекомендовал иметь отдельный сервер, предназначенный только для этого, который действует как чисто функциональный сервер.

Более того, вам может быть полезно взглянуть на sysbox, среду выполнения контейнера, похожую на виртуальную машину, которая обеспечивает более безопасную среду. Sysbox того стоит, особенно если основное приложение работает в контейнере, а это значит, что вы будете запускать Docker в Docker.

Этот метод был выбран в январе, но вскоре возможности языка требовали большего, чем просто передача кода через оболочку контейнера. Кроме того, по какой-то причине память сервера часто перегружается; мы запускаем код внутри самоудаляющихся контейнеров при каждом нажатии клавиши в течение 1 секунды. (Ты можешь лучше!)

Другие варианты

  • Веб-контейнеры
  • MicroVM (Фейерверк)
  • Субхостинг Deno
  • Васмер
  • ShadowRealms

Самый безопасный вариант

Мне особенно нравится Firecracker, но его настройку требует немалых усилий, поэтому, если у вас пока нет времени, на всякий случай сделайте комбинацию статического анализа и выполнения с ограничением времени. . Вы можете использовать esprima для анализа кода и проверки на наличие вредоносных действий.

Как запустить код TypeScript?

Ну, та же история с одним (может быть необязательным) дополнительным шагом: транспилируйте код в JavaScript перед его запуском. Проще говоря, вы можете использовать компилятор esbuild или typescript, а затем продолжить использование вышеуказанных методов.

async function build(userCode: string) {
  const result = await esbuild.build({
    stdin: {
      contents: `${userCode}`,
      loader: 'ts',
      resolveDir: __dirname,
    },
    inject: [
      // In case you want to inject some code
    ],
    platform: 'node',
    write: false,
    treeShaking: false,
    sourcemap: false,
    minify: false,
    drop: ['debugger', 'console'],
    keepNames: true,
    format: 'cjs',
    bundle: true,
    target: 'es2022',
    plugins: [
      nodeExternalsPlugin(), // make all the non-native modules external
    ],
  });
  return result.outputFiles![0].text;
}

Примечания:

  • Комплектеры на основе Rust обычно предлагают версию веб-сборки, что означает, что вы можете транспилировать код в браузере. У Esbuild есть версия веб-сборки.
  • Не включайте в пакет импортируемые пользователем файлы, если вы не внесли их в разрешенный список.

Кроме того, вы можете вообще избежать транспиляции, запустив код с помощью Deno или Bun в контейнере докера, поскольку они поддерживают TypeScript «из коробки».

Заключение

Запуск пользовательского кода — это палка о двух концах. Он может предоставить вашей платформе множество функций и возможностей настройки, но также подвергает вас значительным рискам безопасности. Очень важно понимать риски и принимать соответствующие меры для их смягчения, а также помнить, что чем более изолирована окружающая среда, тем она безопаснее.

Рекомендации

  • Январская мгновенная подборка
  • Запуск ненадежного JavaScript в Node.js
  • Как языки поддерживают выполнение ненадежного пользовательского кода во время выполнения?
  • Безопасная оценка JavaScript с помощью контекстных данных
Заявление о выпуске Эта статья воспроизведена по адресу: https://dev.to/this-is-learning/running-untrusted-javascript-code-3mi3?1. Если есть какие-либо нарушения, свяжитесь с [email protected], чтобы удалить их.
Последний учебник Более>

Изучайте китайский

Отказ от ответственности: Все предоставленные ресурсы частично взяты из Интернета. В случае нарушения ваших авторских прав или других прав и интересов, пожалуйста, объясните подробные причины и предоставьте доказательства авторских прав или прав и интересов, а затем отправьте их по электронной почте: [email protected]. Мы сделаем это за вас как можно скорее.

Copyright© 2022 湘ICP备2022001581号-3