2026年5月27日 12:40

Web Worker 安全攻防:同源策略、CSP 和通信风险

Worker 不是法外之地

很多人以为 Worker 跑在独立线程里,安全性就天然有保障。恰恰相反——Worker 引入了新的攻击面:跨域脚本加载、postMessage 注入、SharedArrayBuffer 竞态,每一个都可能被利用。本文把 Web Worker 相关的安全问题和防御手段讲清楚。

同源策略:第一道防线

Worker 脚本必须和主页面同源(协议 + 域名 + 端口一致)。这是浏览器强制的,不是建议。

javascript
// 跨域加载 → 直接报错 new Worker('https://evil.com/worker.js'); // SecurityError // 同源加载 → 正常 new Worker('/workers/task.js');

但同源策略有绕过方式,而这些绕过方式本身就是安全隐患。

Blob URL 的风险

用 Blob URL 可以绕过同源限制,创建内联 Worker:

javascript
// 从任意字符串创建 Worker const code = 'self.onmessage = (e) => { /* ... */ }'; const blob = new Blob([code], { type: 'text/javascript' }); new Worker(URL.createObjectURL(blob));

问题在于:如果 code 的内容来自用户输入或外部 API,攻击者就能注入任意代码在 Worker 里执行。永远不要用不受信任的数据构造 Worker 脚本

用完后必须 URL.revokeObjectURL() 释放,否则内存泄漏。

importScripts 的跨域加载

Worker 内部可以用 importScripts() 加载外部脚本,这个方法不受同源限制

javascript
// worker.js importScripts('https://cdn.example.com/lib.js'); // 允许跨域

这是个设计选择——Worker 需要加载工具库。但这也意味着如果 CDN 被入侵或者 DNS 被劫持,恶意脚本就跑进了你的 Worker。

防御方式:在服务端配置 Content-Security-Policyscript-src 指令,限制 importScripts 能加载哪些来源的脚本。

CSP 对 Worker 的约束

Worker 有自己的执行上下文,CSP 的约束方式和主页面不同:

  • 同源 Worker 脚本(通过 URL 加载):不受创建它的页面的 CSP 限制
  • Blob/data URL Worker:继承创建它的页面的 CSP 策略
  • Worker 内的 importScripts:受 Worker 自身的 CSP 约束(如果有)

这意味着如果你想限制 Worker 的行为,需要给 Worker 脚本的 HTTP 响应也加上 CSP 头:

shell
Content-Security-Policy: script-src 'self' cdn.example.com

postMessage 通信安全

postMessage 是 Worker 和主线程唯一的通信通道,也是 XSS 注入的潜在入口。

验证消息来源

主线程收到的消息不一定来自你的 Worker。特别是 SharedWorker 和 Service Worker 场景下,多个页面都能发消息:

javascript
// 主线程:验证消息来源和格式 worker.onmessage = (e) => { const data = e.data; // 类型校验 if (typeof data !== 'object' || data === null) return; if (typeof data.type !== 'string') return; // 只处理已知的消息类型 const allowedTypes = ['result', 'progress', 'error']; if (!allowedTypes.includes(data.type)) return; // 处理消息 handleMessage(data); }; // Worker 端同理:验证主线程发来的数据 self.onmessage = (e) => { const data = e.data; if (!data || typeof data.type !== 'string') return; // ... };

不要直接执行消息里的代码

javascript
// 危险!永远不要这么做 self.onmessage = (e) => { eval(e.data.code); // 任意代码执行 new Function(e.data.fn)(); // 同样危险 };

看似明显,但在模板引擎或动态逻辑场景里容易踩进去。如果必须根据消息执行不同逻辑,用白名单映射:

javascript
const handlers = { sort: (data) => { /* ... */ }, filter: (data) => { /* ... */ }, }; self.onmessage = (e) => { const handler = handlers[e.data.type]; if (handler) handler(e.data.params); };

SharedArrayBuffer 的安全门槛

SharedArrayBuffer 允许主线程和 Worker 共享同一块内存,没有序列化开销。但它也带来了竞态条件风险——两个线程同时写同一个内存位置,数据就乱了。

浏览器对 SharedArrayBuffer 有严格的安全要求,服务端必须返回以下两个响应头,否则 new SharedArrayBuffer() 直接抛错:

shell
Cross-Origin-Opener-Policy: same-origin Cross-Origin-Embedder-Policy: require-corp

这两个头不是"建议加",而是强制要求。原因是为了防止 Spectre 类的侧信道攻击——没有这些头,恶意页面可以通过 SharedArrayBuffer 读取跨域内存数据。

如果加上 COEP 后你的页面加载第三方资源(图片、脚本)出错了,需要给这些资源的响应加上 Cross-Origin-Resource-Policy: cross-origin 头。

Worker 里能访问什么、不能访问什么

从安全角度看,Worker 的 API 限制本身就是一种防护:

能访问不能访问安全意义
fetch、WebSocketdocument、DOM不能直接篡改页面
IndexedDBlocalStorage避免同步 I/O 竞态
Cache APIwindow、parent隔离全局作用域
NotificationsXMLHttpRequest推荐用 fetch 替代
performancelocation(只读)不能跳转页面

这些限制意味着即使 Worker 代码被攻破,攻击者也无法直接操作 DOM 或窃取 localStorage 中的 token。Worker 的攻击半径被刻意缩小了。

实际攻击场景

场景 1:CDN 供应链攻击。你的 Worker 用 importScripts('https://cdn.example.com/lib.js'),CDN 被入侵后恶意代码跑进了 Worker。防御:CSP 限制 script-src,或改用 npm 包 + 打包工具。

场景 2:postMessage 中间人。攻击者在页面注入脚本拦截 Worker 通信,篡改消息内容。防御:消息加签名校验,关键字段用加密传输。

场景 3:Blob Worker 代码注入。从服务端获取的配置数据直接拼进 Worker 代码字符串,攻击者通过配置接口注入恶意代码。防御:Worker 代码和数据严格分离,用 postMessage 传配置,不拼字符串。

安全检查清单

  • Worker 脚本是否只从同源加载?如果是 Blob URL,代码来源是否可信?
  • importScripts 加载的外部脚本是否有 CSP 保护?
  • postMessage 通信是否做了类型校验和白名单过滤?
  • 有没有用 evalnew Function 执行消息中的代码?
  • SharedArrayBuffer 是否配了 COOP/COEP 响应头?
  • Worker 脚本 MIME 类型是否为 text/javascript
  • Blob URL 用完后是否调用了 revokeObjectURL
标签:Web Worker