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    

Artikel empfehlen

Lösung zum Hinzufügen einer iptables-Firewall-Richtlinie zum MySQL-Dienst

Wenn Ihre MySQL-Datenbank auf einem CentOS7-Syste...

Empfohlene Plugins und Anwendungsbeispiele für Vue-Unit-Tests

Inhaltsverzeichnis rahmen Erstklassiges Fehlerrep...

So implementieren Sie die Größenanpassung mobiler Webseiten

Ich habe das vorliegende Projekt endlich abgeschl...

So installieren Sie Element UI und verwenden Vektorgrafiken in vue3.0

Hier konzentrieren wir uns nur auf die Installati...

Windows kann den MySQL-Dienst nicht starten und meldet Fehler 1067 – Lösung

Als ich mich bei MySQL anmeldete, wurde mir plötz...

Binäre Typoperationen in MySQL

Dieser Artikel stellt hauptsächlich die binären O...

MySQL-Transaktions-Tutorial Yii2.0 Händler-Auszahlungsfunktion

Vorwort Ich bin ein PHP-Programmierer, der als Pr...

Tutorial zur Installation und Konfiguration von VMware Tools für Ubuntu 18.04

Dieser Artikel beschreibt die Installation und Ko...

Teilen Sie 8 MySQL-Fallstricke, die Sie erwähnen müssen

MySQL ist einfach zu installieren, schnell und ve...

Vue-Projekt @change mehrere Parameter, um mehrere Ereignisse zu übergeben

Erstens gibt es nur ein Änderungsereignis. change...