„Wenn ein Arbeiter seine Arbeit gut machen will, muss er zuerst seine Werkzeuge schärfen.“ – Konfuzius, „Die Gespräche des Konfuzius. Lu Linggong“
Titelseite > Programmierung > Ausführen von nicht vertrauenswürdigem JavaScript-Code

Ausführen von nicht vertrauenswürdigem JavaScript-Code

Veröffentlicht am 29.07.2024
Durchsuche:246

Running Untrusted JavaScript Code

WICHTIG: Hier geht es nur um die Ausführung von JavaScript- und TypeScript-Code. Abgesehen davon könnte das Schreiben auch die Richtung sein, anderen Code in anderen Sprachen auszuführen.

Wenn Sie Benutzern erlauben, ihren Code innerhalb Ihrer Anwendung auszuführen, eröffnet sich eine Welt der Anpassung und Funktionalität, aber es setzt Ihre Plattform auch erheblichen Sicherheitsbedrohungen aus.

Angesichts der Tatsache, dass es sich um Benutzercode handelt, wird alles erwartet, vom Anhalten der Server (es könnten Endlosschleifen sein) bis zum Diebstahl sensibler Informationen.

In diesem Artikel werden verschiedene Strategien zur Reduzierung der Ausführung von Benutzercode untersucht, darunter Web Worker, statische Codeanalyse und mehr …

Es sollte Sie interessieren

Es gibt viele Szenarien, in denen Sie vom Benutzer bereitgestellten Code ausführen müssen, von kollaborativen Entwicklungsumgebungen wie CodeSandbox und StackBiltz bis hin zu anpassbaren API-Plattformen wie January. Sogar Code-Spielplätze sind anfällig für Risiken.

Die beiden wesentlichen Vorteile der sicheren Ausführung von vom Benutzer bereitgestelltem Code sind nämlich:

  1. Das Vertrauen Ihres Benutzers gewinnen: Selbst wenn der Benutzer vertrauenswürdig ist, kann er Code ausführen, der von anderen absichtlich schlechten Personen kopiert wurde.
  2. Sichern Sie Ihre Umgebung: Das Letzte, was Sie brauchen, ist ein Code, der Ihren Server anhält. Denke während (wahr) {}

Definieren Sie „sensible Informationen“

Das Ausführen von Benutzercode ist erst dann schädlich, wenn Sie befürchten, dass dadurch Daten gestohlen werden könnten. Alle Daten, die Ihnen Sorgen bereiten, gelten als vertrauliche Informationen. Beispielsweise handelt es sich bei JWT in den meisten Fällen um vertrauliche Informationen (vielleicht wenn es als Authentifizierungsmechanismus verwendet wird)

Was könnte schiefgehen

Berücksichtigen Sie die potenziellen Risiken von JWT, die in Cookies gespeichert werden, die bei jeder Anfrage gesendet werden. Ein Benutzer könnte versehentlich eine Anfrage auslösen, die das JWT an einen böswilligen Server sendet, und...

  • Cross-Site Scripting (XSS).
  • Denial of Service (DoS)-Angriffe.
  • Datenexfiltration. Ohne angemessene Schutzmaßnahmen können diese Bedrohungen die Integrität und Leistung Ihrer Anwendung beeinträchtigen.

Methoden

Die böse Bewertung

Das einfachste von allen und doch das riskanteste.

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

Wenn Sie diesen Code ausführen, wird diese Nachricht protokolliert. Im Wesentlichen ist eval ein JS-Interpreter, der auf den globalen/Fensterbereich zugreifen kann.

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

Dieser Code verwendet Fetch, der im globalen Bereich definiert ist. Der Interpreter weiß nichts davon, aber da eval auf ein Fenster zugreifen kann, weiß er es. Das bedeutet, dass sich die Ausführung einer Auswertung im Browser von der Ausführung in einer Serverumgebung oder einem Worker unterscheidet.

eval(`document.body`);

Wie wäre es damit...

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

Dieser Code stoppt die Browser-Registerkarte. Sie fragen sich vielleicht, warum ein Benutzer sich das antun würde. Nun, sie kopieren möglicherweise Code aus dem Internet. Aus diesem Grund ist es vorzuziehen, eine statische Analyse mit/oder einer Timebox für die Ausführung durchzuführen.

Vielleicht möchten Sie sich die MDN-Dokumente über eval ansehen

Die Ausführung der Zeitbox kann erfolgen, indem der Code in einem Web-Worker ausgeführt wird und setTimeout verwendet wird, um die Ausführungszeit zu begrenzen.

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) {}');

Funktionskonstruktor

Dies ähnelt eval, ist jedoch etwas sicherer, da es nicht auf den umschließenden Bereich zugreifen kann.

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

Dieser Code protokolliert 2.

Hinweis: Das zweite Argument ist der Funktionskörper.

Der Funktionskonstruktor kann nicht auf den umschließenden Bereich zugreifen, sodass der folgende Code einen Fehler auslöst.

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

Es kann jedoch auf den globalen Bereich zugegriffen werden, sodass das Abrufbeispiel von oben funktioniert.

WebWorker

Sie können „Function Constructor and eval“ auf einem WebWorker ausführen, was aufgrund der Tatsache, dass es keinen DOM-Zugriff gibt, etwas sicherer ist.

Um weitere Einschränkungen einzuführen, sollten Sie erwägen, die Verwendung globaler Objekte wie fetch, XMLHttpRequest, sendBeacon zu verbieten. Lesen Sie in diesem Artikel nach, wie Sie das tun können.

Isolierte VM

Isolated-VM ist eine Bibliothek, die es Ihnen ermöglicht, Code in einer separaten VM (der Isolate-Schnittstelle von v8) auszuführen.

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")');

Dieser Code protokolliert Hallo Welt

WebAssembly

Dies ist eine spannende Option, da sie eine Sandbox-Umgebung zum Ausführen von Code bietet. Eine Einschränkung besteht darin, dass Sie eine Umgebung mit Javascript-Bindungen benötigen. Ein interessantes Projekt namens Extism erleichtert dies jedoch. Vielleicht möchten Sie ihrem Tutorial folgen.

Das Faszinierende daran ist, dass Sie eval verwenden, um den Code auszuführen, aber aufgrund der Natur von WebAssembly sind DOM, Netzwerk, Dateisystem und Zugriff auf die Host-Umgebung nicht möglich (obwohl sie je nach unterschiedlich sein können). die wasm-Laufzeit).

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

module.exports = { evaluate };

Sie müssen den obigen Code zuerst mit Extism kompilieren, wodurch eine Wasm-Datei ausgegeben wird, die in einer Umgebung mit Wasm-Laufzeit (Browser oder node.js) ausgeführt werden kann.

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

Wir wechseln jetzt zur Serverseite. Docker ist eine großartige Option, um Code isoliert vom Host-Computer auszuführen. (Vorsicht vor Container-Flucht)

Sie können Dockerode verwenden, um den Code in einem Container auszuführen.

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'],
  },
});

Denken Sie daran, dass Sie sicherstellen müssen, dass auf dem Server Docker installiert ist und ausgeführt wird. Ich würde empfehlen, einen separaten Server ausschließlich dafür einzurichten, der als reiner Funktionsserver fungiert.

Darüber hinaus könnte es für Sie von Vorteil sein, einen Blick auf sysbox zu werfen, eine VM-ähnliche Container-Laufzeitumgebung, die eine sicherere Umgebung bietet. Sysbox lohnt sich, insbesondere wenn die Haupt-App in einem Container läuft, was bedeutet, dass Sie Docker in Docker ausführen.

Dies war im Januar die Methode der Wahl, aber bald erforderten die Sprachfunktionen mehr als nur die Weiterleitung des Codes durch die Container-Shell. Außerdem kommt es aus irgendeinem Grund häufig zu Spitzenwerten beim Serverspeicher; Wir führen den Code bei jedem 1s-entprellten Tastendruck in selbstentfernbaren Containern aus. (Das kannst du besser!)

Andere Optionen

  • Webcontainer
  • MicroVM (Firecraker)
  • Deno-Subhosting
  • Wasmer
  • ShadowRealms

Sicherste Option

Firecracker gefällt mir besonders gut, aber die Einrichtung ist etwas arbeitsintensiv. Wenn Sie sich die Zeit also noch nicht leisten können, sollten Sie auf Nummer sicher gehen und eine Kombination aus statischer Analyse und zeitgesteuerter Ausführung durchführen . Sie können esprima verwenden, um den Code zu analysieren und auf böswillige Handlungen zu prüfen.

Wie führe ich TypeScript-Code aus?

Nun, die gleiche Geschichte mit einem (könnte optionalen) zusätzlichen Schritt: Transpilieren Sie den Code in JavaScript, bevor Sie ihn ausführen. Einfach ausgedrückt können Sie esbuild oder den Typescript-Compiler verwenden und dann mit den oben genannten Methoden fortfahren.

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;
}

Anmerkungen:

  • Rust-basierte Bundler bieten normalerweise eine Web-Assembly-Version an, was bedeutet, dass Sie den Code im Browser transpilieren können. Esbuild verfügt über eine Webassembly-Version.
  • Fügen Sie keine benutzerdefinierten Importe in das Paket ein, es sei denn, Sie haben sie auf die Zulassungsliste gesetzt.

Darüber hinaus können Sie das Transpilieren ganz vermeiden, indem Sie den Code mit Deno oder Bun in einem Docker-Container ausführen, da diese TypeScript standardmäßig unterstützen.

Abschluss

Das Ausführen von Benutzercode ist ein zweischneidiges Schwert. Es kann viele Funktionen und Anpassungsmöglichkeiten für Ihre Plattform bieten, setzt Sie jedoch auch erheblichen Sicherheitsrisiken aus. Es ist wichtig, die Risiken zu verstehen und geeignete Maßnahmen zu ergreifen, um sie zu mindern. Dabei ist zu bedenken, dass die Umgebung umso sicherer ist, je isolierter sie ist.

Verweise

  • Januar-Sofortzusammenstellung
  • Nicht vertrauenswürdiges JavaScript in Node.js ausführen
  • Wie unterstützen Sprachen die Ausführung nicht vertrauenswürdigen Benutzercodes zur Laufzeit?
  • JavaScript mit Kontextdaten sicher bewerten
Freigabeerklärung Dieser Artikel ist abgedruckt unter: https://dev.to/this-is-learning/running-untrusted-javascript-code-3mi3?1 Bei Verstößen wenden Sie sich bitte an [email protected], um ihn zu löschen
Neuestes Tutorial Mehr>

Haftungsausschluss: Alle bereitgestellten Ressourcen stammen teilweise aus dem Internet. Wenn eine Verletzung Ihres Urheberrechts oder anderer Rechte und Interessen vorliegt, erläutern Sie bitte die detaillierten Gründe und legen Sie einen Nachweis des Urheberrechts oder Ihrer Rechte und Interessen vor und senden Sie ihn dann an die E-Mail-Adresse: [email protected] Wir werden die Angelegenheit so schnell wie möglich für Sie erledigen.

Copyright© 2022 湘ICP备2022001581号-3