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グリッドレイアウトを理解する

推薦する

MySQL のマスター スレーブ レプリケーション オプションをオンラインで変更する方法

序文: MySQL で最も一般的に使用されるアーキテクチャは、マスター スレーブ レプリケーションで...

Windows 10 の Docker で countly-server を展開して実行するプロセス

私は最近countlyに触れて、慣れてきました。私は、必要に応じてcountlyのクラッシュプラグイ...

Springboot プロジェクトに動的にパラメータを渡すための Docker の実装方法

背景最近、Docker 初心者の友人から、毎回プロジェクト構成ファイルにハードコーディングしてサービ...

MySQL で null 値と空文字 ('') を区別する

日常の開発では、データベースの追加、削除、変更、クエリが一般的に行われるため、Mysql で NUL...

Linux で yum と入力した後に -bash: /usr/bin/yum: No such file or directory という問題を解決する方法

Linuxでyumを入力すると、プロンプトが表示されます: -bash: /usr/bin/yum:...

Linux でファイアウォールがオフになっているかどうかを確認する方法

1. サービス方法ファイアウォールのステータスを確認します。 [root@centos6 ~]# サ...

キャンバスをベースにした超クールな水光効果を実現

この記事の例では、キャンバスをベースにした超クールな水の光の効果を実装するための具体的なコードを参考...

Linux で SpringBoot jar プログラム デプロイメント シェル スクリプトを起動および停止する方法

では早速、コードをお見せしましょう。具体的なコードは次のとおりです。 #!/bin/bash cd ...

ミニプログラムはミニプログラムクラウドを使用してWeChatの支払い機能を実装します

目次1. WeChat Payを開く1.1 アフィリエイト加盟店番号1.2 加盟店番号を追加する1....

PHP で JSON バックスラッシュを削除する例

1. 「stripslashes($_POST['json']);」メソッドを使用し...

Vue Element フロントエンドアプリケーション開発のための従来の JS 処理機能

目次1. 従来のコレクションに対するフィルター、マップ、および削減処理方法2. 再帰処理3. for...

nodejsとyarnをインストールし、Taobaoソースプロセスレコードを構成する

目次1. nodejsをダウンロードする2. ダブルクリックしてインストール3. グローバル npm...

MacでのMySQL5.7.22のインストール手順

1. インストールパッケージを使用してMySQLをインストールします(オンラインダウンロードは遅すぎ...

MySQL 8.0 バージョンで getTables がすべてのデータベース テーブルを返す問題の簡単な分析

序文この記事では、主にライブラリ内のすべてのテーブルを返すMysql8.0ドライバgetTables...

Dockerの基礎

序文: Docker はオープンソースのアプリケーション コンテナ エンジンであり、開発者はこれを使...