欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

WebWorker 封裝 JavaScript 沙箱詳情

 更新時(shí)間:2021年10月29日 10:45:38   作者:rxliuli blog  
這篇文章主要介紹了WebWorker 封裝 JavaScript 沙箱,在前文 quickjs 封裝 JavaScript 沙箱詳情 已經(jīng)基于 quickjs 實(shí)現(xiàn)了一個(gè)沙箱,今天這篇文章再基于 web worker 實(shí)現(xiàn)備用方案,需要的朋友可以參考一下

1、場(chǎng)景

在前文  quickjs 封裝 JavaScript 沙箱詳情 已經(jīng)基于 quickjs 實(shí)現(xiàn)了一個(gè)沙箱,這里再基于 web worker 實(shí)現(xiàn)備用方案。如果你不知道 web worker 是什么或者從未了解過(guò),可以查看 Web Workers API。簡(jiǎn)而言之,它是一個(gè)瀏覽器實(shí)現(xiàn)的多線程,可以運(yùn)行一段代碼在另一個(gè)線程,并且提供與之通信的功能。

2、實(shí)現(xiàn) IJavaScriptShadowbox

事實(shí)上,web worker 提供了 event emitter 的 api,即 postMessage/onmessage,所以實(shí)現(xiàn)非常簡(jiǎn)單。

實(shí)現(xiàn)分為兩部分,一部分是在主線程實(shí)現(xiàn) IJavaScriptShadowbox,另一部分則是需要在 web worker 線程實(shí)現(xiàn) IEventEmitter

2.1 主線程的實(shí)現(xiàn)

import { IJavaScriptShadowbox } from "./IJavaScriptShadowbox";

export class WebWorkerShadowbox implements IJavaScriptShadowbox {
  destroy(): void {
    this.worker.terminate();
  }

  private worker!: Worker;
  eval(code: string): void {
    const blob = new Blob([code], { type: "application/javascript" });
    this.worker = new Worker(URL.createObjectURL(blob), {
      credentials: "include",
    });
    this.worker.addEventListener("message", (ev) => {
      const msg = ev.data as { channel: string; data: any };
      // console.log('msg.data: ', msg)
      if (!this.listenerMap.has(msg.channel)) {
        return;
      }
      this.listenerMap.get(msg.channel)!.forEach((handle) => {
        handle(msg.data);
      });
    });
  }

  private readonly listenerMap = new Map<string, ((data: any) => void)[]>();
  emit(channel: string, data: any): void {
    this.worker.postMessage({
      channel: channel,
      data,
    });
  }
  on(channel: string, handle: (data: any) => void): void {
    if (!this.listenerMap.has(channel)) {
      this.listenerMap.set(channel, []);
    }
    this.listenerMap.get(channel)!.push(handle);
  }
  offByChannel(channel: string): void {
    this.listenerMap.delete(channel);
  }
}

2.2 web worker 線程的實(shí)現(xiàn)

import { IEventEmitter } from "./IEventEmitter";

export class WebWorkerEventEmitter implements IEventEmitter {
  private readonly listenerMap = new Map<string, ((data: any) => void)[]>();

  emit(channel: string, data: any): void {
    postMessage({
      channel: channel,
      data,
    });
  }

  on(channel: string, handle: (data: any) => void): void {
    if (!this.listenerMap.has(channel)) {
      this.listenerMap.set(channel, []);
    }
    this.listenerMap.get(channel)!.push(handle);
  }

  offByChannel(channel: string): void {
    this.listenerMap.delete(channel);
  }

  init() {
    onmessage = (ev) => {
      const msg = ev.data as { channel: string; data: any };
      if (!this.listenerMap.has(msg.channel)) {
        return;
      }
      this.listenerMap.get(msg.channel)!.forEach((handle) => {
        handle(msg.data);
      });
    };
  }

  destroy() {
    this.listenerMap.clear();
    onmessage = null;
  }
}

3、使用 WebWorkerShadowbox/WebWorkerEventEmitter

主線程代碼

const shadowbox: IJavaScriptShadowbox = new WebWorkerShadowbox();
shadowbox.on("hello", (name: string) => {
  console.log(`hello ${name}`);
});
// 這里的 code 指的是下面 web worker 線程的代碼
shadowbox.eval(code);
shadowbox.emit("open");


web worker 線程代碼

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


下面是代碼的執(zhí)行流程示意圖;web worker 沙箱實(shí)現(xiàn)使用示例代碼的執(zhí)行流程:

4、限制 web worker 全局 api

經(jīng)大佬 JackWoeker 提醒,web worker 有許多不安全的 api,所以必須限制,包含但不限于以下 api

  • fetch
  • indexedDB
  • performance

事實(shí)上,web worker 默認(rèn)自帶了 276 個(gè)全局 api,可能比我們想象中多很多。

有篇 文章 闡述了如何在 web 上通過(guò) performance/SharedArrayBuffer api 做側(cè)信道攻擊,即便現(xiàn)在 SharedArrayBuffer api 現(xiàn)在瀏覽器默認(rèn)已經(jīng)禁用了,但天知道還有沒(méi)有其他方法。所以最安全的方法是設(shè)置一個(gè) api 白名單,然后刪除掉非白名單的 api。

// whitelistWorkerGlobalScope.ts
/**
 * 設(shè)定 web worker 運(yùn)行時(shí)白名單,ban 掉所有不安全的 api
 */
export function whitelistWorkerGlobalScope(list: PropertyKey[]) {
  const whitelist = new Set(list);
  const all = Reflect.ownKeys(globalThis);
  all.forEach((k) => {
    if (whitelist.has(k)) {
      return;
    }
    if (k === "window") {
      console.log("window: ", k);
    }
    Reflect.deleteProperty(globalThis, k);
  });
}

/**
 * 全局值的白名單
 */
const whitelist: (
  | keyof typeof global
  | keyof WindowOrWorkerGlobalScope
  | "console"
)[] = [
  "globalThis",
  "console",
  "setTimeout",
  "clearTimeout",
  "setInterval",
  "clearInterval",
  "postMessage",
  "onmessage",
  "Reflect",
  "Array",
  "Map",
  "Set",
  "Function",
  "Object",
  "Boolean",
  "String",
  "Number",
  "Math",
  "Date",
  "JSON",
];

whitelistWorkerGlobalScope(whitelist);

然后在執(zhí)行第三方代碼前先執(zhí)行上面的代碼

import beforeCode from "./whitelistWorkerGlobalScope.js?raw";

export class WebWorkerShadowbox implements IJavaScriptShadowbox {
  destroy(): void {
    this.worker.terminate();
  }

  private worker!: Worker;
  eval(code: string): void {
    // 這行是關(guān)鍵
    const blob = new Blob([beforeCode + "\n" + code], {
      type: "application/javascript",
    });
    // 其他代碼。。。
  }
}

由于我們使用 ts 編寫(xiě)源碼,所以還必須將 ts 打包為 js bundle,然后通過(guò) vite 的 ?raw 作為字符串引入,下面吾輩寫(xiě)了一個(gè)簡(jiǎn)單的插件來(lái)完成這件事。

import { defineConfig, Plugin } from "vite";
import reactRefresh from "@vitejs/plugin-react-refresh";
import checker from "vite-plugin-checker";
import { build } from "esbuild";
import * as path from "path";

export function buildScript(scriptList: string[]): Plugin {
  const _scriptList = scriptList.map((src) => path.resolve(src));
  async function buildScript(src: string) {
    await build({
      entryPoints: [src],
      outfile: src.slice(0, src.length - 2) + "js",
      format: "iife",
      bundle: true,
      platform: "browser",
      sourcemap: "inline",
      allowOverwrite: true,
    });
    console.log("構(gòu)建完成: ", path.relative(path.resolve(), src));
  }
  return {
    name: "vite-plugin-build-script",

    async configureServer(server) {
      server.watcher.add(_scriptList);
      const scriptSet = new Set(_scriptList);
      server.watcher.on("change", (filePath) => {
        // console.log('change: ', filePath)
        if (scriptSet.has(filePath)) {
          buildScript(filePath);
        }
      });
    },
    async buildStart() {
      // console.log('buildStart: ', this.meta.watchMode)
      if (this.meta.watchMode) {
        _scriptList.forEach((src) => this.addWatchFile(src));
      }
      await Promise.all(_scriptList.map(buildScript));
    },
  };
}

// https://vitejs.dev/config/
export default defineConfig({
  plugins: [
    reactRefresh(),
    checker({ typescript: true }),
    buildScript([path.resolve("src/utils/app/whitelistWorkerGlobalScope.ts")]),
  ],
});

現(xiàn)在,我們可以看到 web worker 中的全局 api 只有白名單中的那些了。

5、web worker 沙箱的主要優(yōu)勢(shì)

可以直接使用 chrome devtool 調(diào)試
直接支持 console/setTimeout/setInterval api
直接支持消息通信的 api

到此這篇關(guān)于WebWorker 封裝 JavaScript 沙箱詳情的文章就介紹到這了,更多相關(guān)WebWorker 封裝 JavaScript 沙箱內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

最新評(píng)論