WebWorker kapselt JavaScript-Sandbox-Details

WebWorker kapselt JavaScript-Sandbox-Details

1. Szenario

Im vorherigen Artikel „Quickjs kapselt JavaScript-Sandbox-Details ein“, wurde eine Sandbox basierend auf quickjs implementiert. Hier wird eine alternative Lösung basierend auf Web Workern implementiert. Wenn Sie nicht wissen, was web worker ist, oder sich noch nie damit befasst haben, sehen Sie sich Web Workers API an. Kurz gesagt handelt es sich um ein browserimplementiertes Multithreading, das einen Codeabschnitt in einem anderen Thread ausführen und die Möglichkeit zur Kommunikation mit diesem bereitstellen kann.

2. Implementieren Sie IJavaScriptShadowbox

Tatsächlich stellt Web Worker eine event emitter -API bereit, nämlich postMessage/onmessage , sodass die Implementierung sehr einfach ist.

Die Implementierung ist in zwei Teile unterteilt: Einer besteht darin, IJavaScriptShadowbox im Hauptthread zu implementieren, und der andere besteht darin, IEventEmitter im web worker Thread zu implementieren.

2.1 Implementierung des Hauptthreads

importiere { IJavaScriptShadowbox } aus "./IJavaScriptShadowbox";

Exportklasse WebWorkerShadowbox implementiert IJavaScriptShadowbox {
  zerstören(): void {
    dieser.worker.terminate();
  }

  Privatarbeiter!: Arbeiter;
  eval(Code: Zeichenfolge): void {
    const blob = neuer Blob([Code], { Typ: "Anwendung/Javascript" });
    dieser.worker = neuer Worker(URL.createObjectURL(blob), {
      Anmeldeinformationen: "einschließen",
    });
    this.worker.addEventListener("Nachricht", (ev) => {
      const msg = ev.data as { Kanal: Zeichenfolge; Daten: beliebig };
      // konsole.log('msg.data: ', msg)
      wenn (!this.listenerMap.has(msg.channel)) {
        zurückkehren;
      }
      dies.listenerMap.get(msg.channel)!.forEach((handle) => {
        Griff(Nachricht.Daten);
      });
    });
  }

  private schreibgeschützte listenerMap = neue Map<string, ((data: any) => void)[]>();
  emittieren(Kanal: Zeichenfolge, Daten: beliebig): void {
    diese.worker.postMessage({
      Kanal: Kanal,
      Daten,
    });
  }
  ein(Kanal: Zeichenfolge, Handle: (Daten: beliebig) => void): void {
    wenn (!this.listenerMap.has(channel)) {
      dies.listenerMap.set(channel, []);
    }
    dies.listenerMap.get(Kanal)!.push(Handle);
  }
  offByChannel(Kanal: Zeichenfolge): void {
    dies.listenerMap.delete(Kanal);
  }
}

2.2 Implementierung von Web-Worker-Threads

importiere { IEventEmitter } aus "./IEventEmitter";

export Klasse WebWorkerEventEmitter implementiert IEventEmitter {
  private schreibgeschützte listenerMap = neue Map<string, ((data: any) => void)[]>();

  emittieren(Kanal: Zeichenfolge, Daten: beliebig): void {
    postMessage({
      Kanal: Kanal,
      Daten,
    });
  }

  ein(Kanal: Zeichenfolge, Handle: (Daten: beliebig) => void): void {
    wenn (!this.listenerMap.has(channel)) {
      dies.listenerMap.set(channel, []);
    }
    dies.listenerMap.get(Kanal)!.push(Handle);
  }

  offByChannel(Kanal: Zeichenfolge): void {
    dies.listenerMap.delete(Kanal);
  }

  init() {
    onmessage = (ev) => {
      const msg = ev.data as { Kanal: Zeichenfolge; Daten: beliebig };
      wenn (!this.listenerMap.has(msg.channel)) {
        zurückkehren;
      }
      dies.listenerMap.get(msg.channel)!.forEach((handle) => {
        Griff(Nachricht.Daten);
      });
    };
  }

  zerstören() {
    dies.listenerMap.clear();
    bei Nachricht = null;
  }
}

3. Verwenden Sie WebWorkerShadowbox/WebWorkerEventEmitter

Hauptthreadcode

const shadowbox: IJavaScriptShadowbox = neue WebWorkerShadowbox();
shadowbox.on("hallo", (name: string) => {
  console.log(`hallo ${name}`);
});
// Der Code hier bezieht sich auf den Code des Web Worker-Threads unten shadowbox.eval(code);
shadowbox.emit("öffnen");


Code des Web-Worker-Threads

const em = neuer WebWorkerEventEmitter();
em.on("öffnen", () => em.emit("hallo", "liuli"));


Nachfolgend sehen Sie ein schematisches Diagramm des Codeausführungsflusses. web worker Sandbox-Implementierung verwendet den Beispielcodeausführungsfluss:

4. Begrenzen Sie die globale API des Web Workers

Wie JackWoeker in Erinnerung rief, verfügen web worker über viele unsichere APIs, die daher eingeschränkt werden müssen, unter anderem die folgenden APIs

  • fetch
  • indexedDB
  • performance

Tatsächlich sind web worker standardmäßig mit 276 globalen APIs ausgestattet, was viel mehr sein kann, als wir denken.

Es gibt einen Artikel, der erklärt, wie man Side-Channel-Angriffe im Web über performance/SharedArrayBuffer api durchführt. Auch wenn在SharedArrayBuffer api jetzt in Browsern standardmäßig deaktiviert ist, wer weiß, ob es andere Möglichkeiten gibt. Der sicherste Weg besteht daher darin, eine API-Whitelist einzurichten und dann die nicht auf der Whitelist stehenden APIs zu löschen.

// whitelistWorkerGlobalScope.ts
/**
 * Legen Sie die Whitelist der Web Worker-Laufzeit fest, um alle unsicheren APIs zu sperren.
 */
Exportfunktion whitelistWorkerGlobalScope(Liste: PropertyKey[]) {
  const whitelist = neues Set(Liste);
  const all = Reflect.ownKeys(globalThis);
  alle.fürJeden((k) => {
    wenn (whitelist.has(k)) {
      zurückkehren;
    }
    wenn (k === "Fenster") {
      console.log("Fenster: ", k);
    }
    Reflect.deleteProperty(globalThis, k);
  });
}

/**
 * Whitelist globaler Werte */
Konstante Whitelist: (
  | Schlüssel von Typ von global
  | Schlüssel von WindowOrWorkerGlobalScope
  | "Konsole"
)[] = [
  "globalThis",
  "Konsole",
  "Zeitlimit festlegen",
  "Zeitüberschreitung löschen",
  "Intervall festlegen",
  "Löschintervall",
  "Nachricht senden",
  "auf Nachricht",
  "Reflektieren",
  "Anordnung",
  "Karte",
  "Satz",
  "Funktion",
  "Objekt",
  "Boolesch",
  "Zeichenfolge",
  "Nummer",
  "Mathe",
  "Datum",
  "JSON",
];

whitelistWorkerGlobalScope(Whitelist);

Führen Sie dann den obigen Code aus, bevor Sie den Code von Drittanbietern ausführen

importiere beforeCode aus "./whitelistWorkerGlobalScope.js?raw";

Exportklasse WebWorkerShadowbox implementiert IJavaScriptShadowbox {
  zerstören(): void {
    dieser.worker.terminate();
  }

  Privatarbeiter!: Arbeiter;
  eval(Code: Zeichenfolge): void {
    // Diese Zeile ist der Schlüssel const blob = new Blob([beforeCode + "\n" + code], {
      Typ: "Anwendung/Javascript",
    });
    // Anderer Code. . .
  }
}

Da wir ts zum Schreiben des Quellcodes verwenden, müssen wir ts auch in js bundle packen und es dann als String über vite ? raw importieren. Unten haben wir ein einfaches Plugin geschrieben, um dies zu tun.

importiere { defineConfig, Plugin } von "vite";
importiere reactRefresh von "@vitejs/plugin-react-refresh";
Importprüfer von „vite-plugin-checker“;
importiere { build } von "esbuild";
importiere * als Pfad von „Pfad“;

Exportfunktion BuildScript (ScriptList: String []): Plugin {
  const _scriptList = scriptList.map((src) => Pfad.auflösen(src));
  asynchrone Funktion BuildScript(src: string) {
    warte auf Build({
      Eintragspunkte: [src],
      Ausgabedatei: src.slice(0, src.length - 2) + "js",
      Format: "iife",
      Bündel: wahr,
      Plattform: "Browser",
      Quellzuordnung: "inline",
      allowOverwrite: true,
    });
    console.log("Build abgeschlossen: ", Pfad.relative(Pfad.resolve(), src));
  }
  zurückkehren {
    Name: "vite-plugin-build-script",

    async konfigurierenServer(server) {
      server.watcher.add(_scriptList);
      const scriptSet = neues Set(_scriptList);
      server.watcher.on("change", (Dateipfad) => {
        // console.log('ändern: ', Dateipfad)
        wenn (scriptSet.has(Dateipfad)) {
          BuildScript(Dateipfad);
        }
      });
    },
    asynchroner BuildStart() {
      // console.log('buildStart: ', this.meta.watchMode)
      wenn (dieser.meta.watchMode) {
        _scriptList.forEach((src) => this.addWatchFile(src));
      }
      warte auf Promise.all(_scriptList.map(buildScript));
    },
  };
}

// https://vitejs.dev/config/
exportiere Standard-DefineConfig({
  Plugins: [
    reagierenRefresh(),
    Prüfer({ typescript: true }),
    BuildScript ([Pfad.auflösen("src/utils/app/whitelistWorkerGlobalScope.ts")]),
  ],
});

Jetzt können wir sehen, dass die globalen APIs im web worker nur diejenigen auf der Whitelist sind.

5. Die wichtigsten Vorteile der Web Worker Sandbox

Sie können chrome devtool zum direkten Debuggen verwenden und console/setTimeout/setInterval api
unterstützen. console/setTimeout/setInterval api
api , die die Nachrichtenkommunikation direkt unterstützt

Dies ist das Ende dieses Artikels über die Details der JavaScript-Sandbox von WebWorker. Weitere verwandte Inhalte zur JavaScript-Sandbox von WebWorker finden Sie in früheren Artikeln auf 123WORDPRESS.COM oder durchsuchen Sie die verwandten Artikel weiter unten. Ich hoffe, dass jeder 123WORDPRESS.COM in Zukunft unterstützen wird!

Das könnte Sie auch interessieren:
  • Quickjs kapselt JavaScript-Sandbox-Details
  • JavaScript-Sandbox-Erkundung
  • Ein kurzer Vortrag über JavaScript Sandbox
  • Eine kurze Diskussion über verschiedene Möglichkeiten zur Implementierung einer Front-End-JS-Sandbox
  • Eine kurze Diskussion über die Node.js-Sandbox-Umgebung
  • Einrichten einer sicheren Sandbox-Umgebung für Node.js-Anwendungen
  • Beispiel für den Sandbox-Modus beim Schließen der JS-Implementierung
  • Beispielanalyse des JS-Sandbox-Modus
  • Sicherheits-Sandbox-Modus des JavaScript-Entwurfsmusters

<<:  Verstehen Sie das CSS3-Rasterlayout in 10 Minuten

>>:  Warum MySQL die Verwendung von Unterabfragen und Verknüpfungen nicht empfiehlt

Artikel empfehlen

Lösung für 404-Fehler beim Herunterladen einer APK-Datei vom IIS-Server

Bei der Verwendung von IIS als Server wurde die A...

Detaillierte Verwendung des Linux-Textsuchbefehls find

Der Befehl „Find“ wird hauptsächlich zum Suchen v...

Ubuntu 19.10 aktiviert SSH-Dienst (detaillierter Prozess)

Ich habe mehr als eine Stunde gebraucht, um SSH i...

Vue implementiert eine Zeit-Countdown-Funktion

In diesem Artikelbeispiel wird der spezifische Co...

JDBC-Erkundung SQLException-Analyse

1. Übersicht über SQLException Wenn bei der Verwe...

Beispielcode für HTML-Listenfeld, Textfeld und Dateifeld

Dropdown-Feld, Textfeld, Dateifeld Der obere Teil...

Detaillierte Erklärung des Missverständnisses zwischen MySQL und Oracle

Inhaltsverzeichnis Wesentlicher Unterschied Daten...

Implementierungsbeispiel für Scan-Code-Zahlung im Vue-Projekt (mit Demo)

Inhaltsverzeichnis Nachfragehintergrund Gedankena...

So implementieren Sie Element-Floating und Clear-Floating mit CSS

Grundlegende Einführung in das Floating Im Standa...

Lösung für das Problem des MySQL-Master-Slave-Switch-Kanals

Nach der VIP-Konfiguration wird beim Aktiv/Standb...