WebWorkerはJavaScriptサンドボックスの詳細をカプセル化します

WebWorkerはJavaScriptサンドボックスの詳細をカプセル化します

1. シナリオ

前回の記事では、Quickjs が JavaScript サンドボックスの詳細をカプセル化し、 quickjsに基づいてサンドボックスが実装されました。ここでは、Web ワーカーに基づいて代替ソリューションが実装されています。 web worker何であるか分からない場合、または一度も調べたことがない場合は、 Web Workers API確認してください。つまり、これはブラウザに実装されたマルチスレッドであり、別のスレッドでコードを実行し、そのコードと通信する機能を提供します。

2. IJavaScriptShadowboxを実装する

実際、Web Worker はpostMessage/onmessageというevent emitter API を提供しているため、実装は非常に簡単です。

実装は 2 つの部分に分かれており、1 つはメイン スレッドでIJavaScriptShadowboxを実装すること、もう 1 つはweb workerスレッドでIEventEmitter実装することです。

2.1 メインスレッドの実装

「./IJavaScriptShadowbox」から {IJavaScriptShadowbox} をインポートします。

エクスポートクラス WebWorkerShadowbox は IJavaScriptShadowbox を実装します {
  破棄(): void {
    this.worker.terminate();
  }

  民間労働者!:労働者;
  eval(コード: 文字列): void {
    const blob = new Blob([code], { type: "application/javascript" });
    this.worker = 新しいWorker(URL.createObjectURL(blob), {
      資格情報: "include",
    });
    this.worker.addEventListener("メッセージ", (ev) => {
      const msg = ev.data as { チャネル: 文字列; データ: 任意 };
      // console.log('msg.data: ', msg)
      if (!this.listenerMap.has(msg.channel)) {
        戻る;
      }
      this.listenerMap.get(msg.channel)!.forEach((handle) => {
        ハンドル(msg.data);
      });
    });
  }

  プライベート読み取り専用リスナーマップ = 新しい Map<string, ((data: any) => void)[]>();
  出力(チャンネル: 文字列、データ: 任意): void {
    this.worker.postMessage({
      チャンネル: チャンネル、
      データ、
    });
  }
  on(チャンネル: 文字列、ハンドル: (データ: 任意) => void): void {
    if (!this.listenerMap.has(channel)) {
      this.listenerMap.set(チャンネル、[]);
    }
    this.listenerMap.get(チャンネル)!.push(ハンドル);
  }
  offByChannel(チャンネル: 文字列): void {
    this.listenerMap.delete(チャンネル);
  }
}

2.2 Webワーカースレッドの実装

「./IEventEmitter」から IEventEmitter をインポートします。

エクスポートクラスWebWorkerEventEmitterはIEventEmitterを実装します{
  プライベート読み取り専用リスナーマップ = 新しい Map<string, ((data: any) => void)[]>();

  出力(チャンネル: 文字列、データ: 任意): void {
    postMessage({
      チャンネル: チャンネル、
      データ、
    });
  }

  on(チャンネル: 文字列、ハンドル: (データ: 任意) => void): void {
    if (!this.listenerMap.has(channel)) {
      this.listenerMap.set(チャンネル、[]);
    }
    this.listenerMap.get(チャンネル)!.push(ハンドル);
  }

  offByChannel(チャンネル: 文字列): void {
    this.listenerMap.delete(チャンネル);
  }

  初期化() {
    onmessage = (ev) => {
      const msg = ev.data as { チャネル: 文字列; データ: 任意 };
      if (!this.listenerMap.has(msg.channel)) {
        戻る;
      }
      this.listenerMap.get(msg.channel)!.forEach((handle) => {
        ハンドル(msg.data);
      });
    };
  }

  破壊する() {
    このリスナーマップをクリアします。
    onmessage = null;
  }
}

3. WebWorkerShadowbox/WebWorkerEventEmitterを使用する

メインスレッドコード

const シャドウボックス: IJavaScriptShadowbox = new WebWorkerShadowbox();
shadowbox.on("hello", (名前: 文字列) => {
  console.log(`hello ${name}`);
});
// ここでのコードは、shadowbox.eval(code); の下の Web ワーカー スレッドのコードを参照します。
shadowbox.emit("open");


Web ワーカー スレッド コード

const em = 新しい WebWorkerEventEmitter();
em.on("open", () => em.emit("hello", "liuli"));


以下はコード実行フローの概略図です。Web web workerサンドボックス実装では、サンプル コード実行フローが使用されます。

4. WebワーカーのグローバルAPIを制限する

JackWoeker指摘したように、 web workerは安全でないAPIが多数あるため、以下のAPIを含むがこれらに限定されないAPIを制限する必要がある。

  • fetch
  • indexedDB
  • performance

実際、 web workerにはデフォルトで276グローバル API が付属しており、これは私たちが考えているよりもはるかに多い可能性があります。

performance/SharedArrayBuffer apiを介して Web 上でサイドチャネル攻撃を実行する方法を説明した記事があります。SharedArrayBuffer在SharedArrayBuffer api現在ブラウザーでデフォルトで無効になっていますが、他の方法があるかどうかは誰にもわかりません。したがって、最も安全な方法は、API ホワイトリストを設定してから、ホワイトリストに登録されていない API を削除することです。

// ホワイトリストWorkerGlobalScope.ts
/**
 * Webワーカーランタイムのホワイトリストを設定して、安全でないAPIをすべて禁止する
 */
エクスポート関数 whitelistWorkerGlobalScope(list: PropertyKey[]) {
  const ホワイトリスト = 新しい Set(リスト);
  const all = Reflect.ownKeys(globalThis);
  すべて.forEach((k) => {
    if (ホワイトリスト.has(k)) {
      戻る;
    }
    if (k === "ウィンドウ") {
      console.log("ウィンドウ: ", k);
    }
    Reflect.deleteProperty(globalThis, k);
  });
}

/**
 * グローバル値のホワイトリスト */
定数ホワイトリスト: (
  | キーof タイプof グローバル
  | WindowOrWorkerGlobalScope のキー
  | 「コンソール」
)[] = [
  "グローバルこれ",
  "コンソール"、
  "タイムアウトの設定",
  「タイムアウトをクリア」、
  "setInterval"、
  「クリア間隔」、
  「ポストメッセージ」、
  "オンメッセージ",
  "反映する"、
  "配列"、
  "地図"、
  "セット"、
  "関数"、
  "物体"、
  「ブール値」、
  "弦"、
  "番号"、
  "数学"、
  "日付"、
  「JSON」、
];

ホワイトリストWorkerGlobalScope(ホワイトリスト);

次に、サードパーティのコードを実行する前に上記のコードを実行します。

「./whitelistWorkerGlobalScope.js?raw」からbeforeCodeをインポートします。

エクスポートクラス WebWorkerShadowbox は IJavaScriptShadowbox を実装します {
  破棄(): void {
    this.worker.terminate();
  }

  民間労働者!:労働者;
  eval(コード: 文字列): void {
    // この行がキーです const blob = new Blob([beforeCode + "\n" + code], {
      タイプ: "application/javascript",
    });
    // その他のコード。 。 。
  }
}

ソース コードの記述には ts を使用するため、 ts をjs bundleにパッケージ化し、それをviteの ? rawを通じて文字列としてインポートする必要もあります。以下では、これを行うための簡単なプラグインを作成しました。

「vite」からdefineConfigとPluginをインポートします。
「@vitejs/plugin-react-refresh」から reactRefresh をインポートします。
「vite-plugin-checker」からチェッカーをインポートします。
「esbuild」から{build}をインポートします。
"path" から * をパスとしてインポートします。

エクスポート関数buildScript(scriptList: string[]): プラグイン{
  _scriptList を scriptList.map((src) => path.resolve(src));
  非同期関数buildScript(src: 文字列) {
    ビルドを待つ({
      エントリポイント: [src],
      出力ファイル: src.slice(0, src.length - 2) + "js",
      フォーマット: "iife",
      バンドル: true、
      プラットフォーム:「ブラウザ」、
      ソースマップ: "インライン",
      上書きを許可する: true、
    });
    console.log("ビルドが完了しました: ", path.relative(path.resolve(), src));
  }
  戻る {
    名前: "vite-plugin-build-script",

    非同期configureServer(サーバー) {
      server.watcher.add(_scriptList);
      _scriptList を新しい Set に追加します。
      server.watcher.on("change", (filePath) => {
        // console.log('変更: ', ファイルパス)
        スクリプトセットにファイルパスがある場合
          ビルドスクリプト(ファイルパス);
        }
      });
    },
    非同期ビルド開始() {
      // console.log('buildStart: ', this.meta.watchMode)
      if (this.meta.watchMode) {
        _scriptList.forEach((src) => this.addWatchFile(src));
      }
      Promise.all(_scriptList.map(buildScript)) を待機します。
    },
  };
}

// https://vitejs.dev/config/
デフォルトのdefineConfigをエクスポートする({
  プラグイン: [
    反応リフレッシュ()、
    チェッカー({typescript: true})、
    ビルドスクリプト([path.resolve("src/utils/app/whitelistWorkerGlobalScope.ts")]),
  ]、
});

これで、 web worker内のグローバル API はホワイトリストにあるものだけであることがわかります。

5. Webワーカーサンドボックスの主な利点

chrome devtoolを使用して直接デバッグし、 console/setTimeout/setInterval api
をサポートできます。 console/setTimeout/setInterval api
メッセージ通信を直接サポートするapi

WebWorker カプセル化 JavaScript サンドボックスの詳細に関するこの記事はこれで終わりです。WebWorker カプセル化 JavaScript サンドボックスに関する関連コンテンツの詳細については、123WORDPRESS.COM の以前の記事を検索するか、以下の関連記事を引き続き参照してください。今後とも 123WORDPRESS.COM をよろしくお願いいたします。

以下もご興味があるかもしれません:
  • Quickjs は JavaScript サンドボックスの詳細をカプセル化します
  • JavaScript サンドボックスの探索
  • JavaScript Sandboxについての簡単な説明
  • フロントエンドJSサンドボックスを実装するいくつかの方法についての簡単な説明
  • Node.jsサンドボックス環境についての簡単な説明
  • Node.js アプリケーション用の安全なサンドボックス環境の設定
  • JS実装クロージャにおけるサンドボックスモードの例
  • JS サンドボックス モードの例の分析
  • JavaScript デザインパターン セキュリティ サンドボックス モード

<<:  Docker の Windows ストレージ パス設定操作

>>:  10分でCSS3グリッドレイアウトを理解する

推薦する

無効と読み取り専用の機能と違い

1: readonly は、このコントロールをロックして、インターフェイス上で変更できないようにしま...

nginx でのリクエストのカウント追跡の簡単な分析

まずは適用方法を説明します。nginxモジュールにはjtxyとjtcmdの2つがあります。 http...

MySQL空間関数を使用してロケーションパンチインを実装するための完全な手順

序文プロジェクトの要件は、ユーザーの現在の位置が特定の地理的位置範囲内にあるかどうかを判断することで...

JavaScript オブジェクト指向クラス継承ケースの説明

1. オブジェクト指向のクラス継承これまでの章では、JavaScript のオブジェクト モデルがプ...

JavaScript を使用して文字列内の最も繰り返しの多い文字を取得する方法

目次トピック分析する使用目的解決:コードは次のように実装されます。分析:配列とポインタ解決:コードは...

HTMLは無効なテーブル幅設定の問題を解決します

テーブルに table-layer:fixed スタイルを設定し、テーブル内の行が結合されていること...

Docker はクラスター MongoDB 実装手順を構築します

序文会社の業務上のニーズにより、独自の MongoDB サービスを構築する予定です。MongoDB ...

Nodejs がイントラネット侵入サービスを実装

目次1. LAN内のプロキシ2. イントラネットの浸透イントラネット侵入とは何ですか?橋プロキシサー...

最新の仮想マシン VMware 14 インストール チュートリアル

まず、VMware 14のアクティベーションコードをお渡ししますFF31K-AHZD1-H8ETZ-...

Windows システムに mysql5.7.21 をインストールするための詳細なチュートリアル

MySQL インストーラーは、MySQL ソフトウェアのあらゆるニーズに対応する、使いやすいウィザー...

Centos7のホスト名を変更する3つの方法

方法 1: hostnamectl の変更ステップ1 ホスト名を確認するホスト名ステップ2 ホスト名...

10分で始めるCSS3アニメーション

導入アニメーションを使用すると、JavaScript や jQuery に依存せずに、純粋な CSS...

HTML+CSS+JS でスタックカルーセル効果を実装するサンプルコード

効果:スライドショーが一方向に動く場合、各画像のサイズ、位置、透明度、レベルを変更する必要があります...

Vue でよく使われる高階関数と包括的な例

1. 配列のよく使われる高階関数配列があり、その配列に対して次の操作を実行したいとします。 100 ...

MySQLは複数テーブル関連統計(サブクエリ統計)の例を実装します

この記事では、例を使用して、MySQL で複数テーブルの関連統計を実装する方法について説明します。ご...