返回所有文章
发布于 · 作者 Renaud Deraison

死不掉的 service worker,和不在乎它的虚拟机

上周谷歌不小心把一个四年前的 Chromium 漏洞重新公开了——一个 service worker 在浏览器关闭后仍然在跑 JavaScript,影响所有主流 Chromium 浏览器,至今未打补丁。概念验证已经流到了野外。有意思的问题不在于它怎么工作,而在于:当你关闭标签页时,它所依附的整台底层机器就会消失——在这样的浏览器上,「持久化」到底意味着什么。

持久化不是代码的属性。它是代码被允许栖身的那个地方 的属性。一个「永远不死」的 service worker,当它被安装 其上的那台机器在你每次关窗口时都被销毁,它就有麻烦 了。

2026 年 5 月 20 日,Chromium 的 issue tracker 悄悄地解除了 一个自 2022 年起就处于受限状态的漏洞的访问限制。几小时 之内,这个漏洞——连同一份可用的概念验证——就被全网镜像、 归档、撰文报道。谷歌随后又把这个 issue 关了回去。已经太 晚了;那个页面已经进了 Wayback MachineBleepingComputer 的报道 也在第二天上线了。

这个漏洞是真的,概念验证是能跑的,距离最初报告已经四年 之后,它在 Chrome Dev 150 和 Edge 148 里依然没有补丁。每 一个 Chromium 衍生的浏览器都受影响:Chrome、Edge、Brave、 Opera、Vivaldi、Arc。

它让一个页面能做的事情,作为一句话来说很普通:注册一个 service worker——一小段浏览器同意让它在后台一直运行的 JavaScript——这个 worker 在页面关闭时不会停,在标签页关 闭时不会停,在浏览器关闭时不会停,在机器重启时也不会 停。研究员 Lyra Rebane 在 2022 年 12 月最初报告了这个漏 洞,她向 BleepingComputer 总结了最坏的情形:在 Microsoft Edge 上,下载菜单根本不会出现, 结果就是「完全静默的 JS RCE,在你关闭浏览器之后还在 跑」。

这个漏洞之所以存在的技术细节,远没有它所启用的那一类攻 击重要。它是一个持久化原语。你曾经访问过一次的某个页 面——也许是几个月之前,也许是一个你不记得自己看到过的 广告——被允许在你的电脑上无限期地继续执行代码。僵尸网络 入伍、广告欺诈、加密货币挖矿劫持、参与 DDoS、慢速数据外 泄、每当你再次登录同一个网站时机会主义地收割凭证——一旦 你拥有了一个能在浏览器退出后存活下来的立足点,这一切都 变得轻而易举。

它是一个好漏洞,从它具有启发性的意义上说。它是一个坏漏 洞,从它一定会被利用的意义上说。

service worker 到底是什么

大约十年来,Web 平台一直允许页面安装一小段叫做 service worker 的 JavaScript 程序。它们在后台运行,独立于任何标 签页之外,它们存在的目的是让 Web 应用能做一些合理的事 情——缓存资源以便你离线时站点也能加载,投递推送通知,在 网络恢复时同步数据。

一个 service worker 是注册在某个 origin 上的(比如说, https、主机名 example.com 和默认端口的组合)。一旦 注册了,浏览器就把它存到磁盘上,并在你下次访问该 origin 上任何东西时把它带回来——又或者,对于像推送消息这种后台 功能,浏览器会自己周期性地把它唤醒。从用户的视角看,这 个 worker 是不可见的。从操作系统的视角看,这个 worker 是浏览器的一部分,以浏览器进程所拥有的任何权限运行。

1. 页面注册 workernavigator.serviceWorker.register('/sw.js')2. 浏览器把它持久化写入磁盘上的配置文件,以 origin 为键,作用域限定为 URL 路径,能在重启后存活。3. 浏览器唤醒它在重访时、在 push 到达时、在后台同步触发时磁盘上的浏览器配置文件cookie · localStorage · IndexedDB · service-worker 注册项——「持久化」真正栖身的地方——
一个 service worker 的正常生命。一个页面注册了它,浏览器把它写到磁盘上、与该页面的 origin 关联起来,从此以后浏览器就负责把这个 worker 重新唤醒——当用户重访 origin 时、当一条 push 到达时、当一次后台同步触发时。worker 的地址是你的本地磁盘;worker 的寿命是托管它的那个浏览器配置文件的寿命。

Rebane 报告的这个漏洞,概括地说,就是一个恶意页面可以滥 用一个后台下载 API 来注册一个 worker,浏览器随后就拒绝 终止它。同一族的 Chromium service-worker 漏洞之前也出过 货;这至少是过去十二个月里的第二个。这种模式——页面种下 点东西,浏览器把它存起来,浏览器之后自己又把它带回来—— 才是值得抓住不放的部分。也正是这种模式,在浏览器一旦不 再拥有一片长期存活的磁盘时,就会一头撞上一堵墙。

持久化需要什么

持久化——在安全意义上,不是 Web 平台意义上——是攻击者在 拿到初始访问之后立刻要做的那一步动作。一个页面或一个二 进制文件落到了你的机器上,在做任何其他有趣的事之前,它 会先安排自己的某一部分在那些显而易见的清理动作里幸存下 来:关闭标签页、退出浏览器、重启、回家路上在地铁上把笔 记本盖上睡眠。在桌面操作系统上,持久化有它自己的一套词 汇——macOS 上的 launch agent,Windows 上的计划任务, Linux 上的 systemd 用户单元,浏览器扩展,自启动条目。

一个 service worker,实际上,就是一个浏览器内部的 launch agent。它被注册、被存储,并按照浏览器所控制的节奏被带回 人间。它是 Web 平台所提供的更优雅的持久化原语之一,这也 是它持续吸引各种漏洞的部分原因。

至关重要的是,那张清单上每一种持久化形式都有同一个要 求,用三个字说清楚:必须有东西能持久。launch agent 能持久,是因为有一个文件系统让 agent 的 .plist 文件被 写入,而 agent 在每次开机时又被从那个文件系统读回来。 service worker 能持久,是因为有一个浏览器配置文件让 worker 的注册项被写入,而那个注册项在每次浏览器启动时被 读回来。把存储这一层拿掉,「持久化」也就跟着没了。

那一句话——把存储这一层拿掉,持久化也就跟着没了——正是 Bromure 围绕其构建的那部分架构。

Bromure 是怎么跑一个浏览器的

一个 Bromure 会话不是一个进程。它是一台虚拟机。当你打开 一个浏览器窗口时,宿主会在 Apple Silicon 的 hypervisor 上启动一个小小的、可随时丢弃的 Linux guest。你的标签页 所在的浏览器进程,是跑在那个 guest 里面的。这个 guest 有它自己的文件系统、自己的内存、自己的内核。这些东西没 有一个属于宿主。

这个架构里有两个部分,对这个 service worker 漏洞特别重 要。

第一,一个 service worker 注册项通常会栖身的磁盘上的配 置文件,是在 guest 的文件系统里,而不在你 Mac 的文件系 统里。当 guest 消失时,那个文件系统也跟着消失。第二, guest 本身的寿命是由用户来设定的——默认情况下,一个 guest 绑定到某个配置文件上(工作、个人、银行、一个 LLM 编码沙箱),并在用户选择时被擦除,但对于临时标签页, guest 在窗口关闭时就被销毁。即使在配置文件模式下, guest 也只是一个磁盘卷快照,用户可以在任何时候把它回滚 回去。

传统浏览器你的用户 · 你的磁盘标签页访问恶意页面注册 service workerWorker 写入配置文件~/Library/Application Support/...在你真正的磁盘上你关掉了标签页……worker 还留在磁盘上……浏览器之后又把它唤醒下次启动时、push 到达时、同步触发时——那段 JS 一直在跑。一跑就是几个月。Bromure可丢弃的 guest VMGUEST 文件系统标签页访问恶意页面注册 service workerWorker 写入 guest 磁盘在 VM 内部——不在宿主上从未碰过你 Mac 的文件系统你关掉了标签页VM 被销毁guest 的磁盘随它一起没了。没有什么可以被唤醒。
同一个恶意 service worker 注册在两个浏览器上。在传统浏览器里,注册项被写入了用户宿主磁盘上的一个配置文件;关闭浏览器并不会把它清掉,下次启动时浏览器又把这个 worker 带了回来。在 Bromure 里,注册项被写入了一个 guest VM 的文件系统;关闭标签页就销毁了这个 VM,注册项也随之消失。

在可丢弃的情形下,那个「永远不死」的 worker 没有什么地 方可以「不死」。它本该在下次浏览器启动时被重新读出来的 那一行磁盘记录,是在一台不再存在的虚拟机里面。那段「在 浏览器关闭之后还在跑」的 JavaScript,之所以还能跑,只是 因为有东西让它继续跑在上面。当浏览器关闭也意味着关闭了 底下那台机器时,worker 就和浏览器同时停下——正是原始规 范所设想的那种行为。

在配置文件的情形下——比如说,你保留了一个长期存活的「社 交媒体」配置文件,它就是_设计来_在会话之间记住你的——那 个 worker 还是会像它一直以来那样存活下去,作用域限定在 那一个配置文件的 VM 内部。它仍然看不到你的「文稿」、看 不到你的钥匙串、看不到你的其他配置文件,也看不到你电脑 上其余的任何东西。而且如果你哪天怀疑那个配置文件捡到了 什么不该捡的东西,你可以把整台 VM 回滚到它上一个干净快 照,所需的时间大致跟重新启动一个浏览器差不多。

一般性的论点

把上面这些读成「Bromure 恰巧打败了这个具体的 service- worker 漏洞」是很容易的。这没错,但这不是有意思的那个论 点。有意思的那个论点更具一般性,而这次事件是支持它的一 小块证据。

浏览器零日还会继续出现。这至少是过去十二个月里的第二个 Chromium service-worker 漏洞。 2024 年和 2025 年那些大的 是 V8 的类型混淆和 Mojo 沙箱逃逸,被以六位数的价格买 下,被商业间谍软件厂商和与国家相关的组织所使用。在 service worker 存在之前就有渲染器被攻陷;在这个具体漏洞 被打补丁很久之后,渲染器仍然会被攻陷。找到新漏洞的经济 账——尤其是现在 Mythos 级别的 AI 审计员 已经在披露窗口的两侧都参与进来——正在向不利于防御者的方 向移动,而不是向有利于防御者的方向移动。

当一个零日开火时,相关的问题不是_浏览器是不是完美无瑕_。 它不是。相关的问题是_漏洞利用实际上够到了什么_。

墙 = 进程内沙箱渲染器(V8、Blink、编解码器……)沙箱(Mojo IPC、seccomp)和漏洞同源的地址空间浏览器进程(broker)你的宿主文稿 · 钥匙串 · SSH 密钥 · ~/.awsiCloud · 摄像头 · 麦克风 · 本地网络——沙箱逃逸所能够到的——墙 = 独立的 VM渲染器(V8、Blink、编解码器……)浏览器进程(broker)——在 guest 内HYPERVISOR 边界独立的内核,独立的地址空间,与漏洞没有共享的字节你的宿主文稿、钥匙串、SSH 密钥、~/.aws ——这些 guest 都看不见。——沙箱逃逸够到的:更多的 guest——
一个浏览器漏洞开火之后,攻击者扩大其触及范围的两种方式。在传统浏览器上,进程内沙箱就是渲染器代码与宿主之间的那堵墙;当有人在 V8 或者某个解析器里找到一个内存漏洞时,裂开的就是那堵墙。在 Bromure 上,那堵墙是一台独立的 VM,它不与浏览器共享地址空间、不解析 Web 字节,也不会因为渲染器出问题就跟着出问题。

一次 Chromium 沙箱逃逸——也就是过去十年里你每隔几个月就 会在新闻里读到的那种典型的「浏览器零日」——是一条链:拿 渲染器里某个部分(V8、Skia、WebP、Dawn、libxml2)的一个 内存漏洞,用它来说服浏览器的 broker 进程做一件渲染器本 不该被允许去做的事。这些漏洞之所以以六位数的价格被买 下,是因为它们要跨过 Chromium 工程师把大部分时间都花在 维护的那堵墙:进程内沙箱。

那堵墙是了不起的工程作品。但从血缘上说,它和跨过它的那 个漏洞是同一类东西做的——C++、共享地址空间、像 Mojo 这样 的 IPC 原语。当 V8 或者 Chromium 捆绑的上百个解析器里出 现一个新的内存漏洞时,那种东西如果给它足够多的功夫,是 可以把沙箱撬开的。

一台 VM 是另一种墙。跑在它里面的浏览器没有一个能映射到 宿主内核的虚拟内存地址;宿主内核不解析浏览器正在解析的 页面;虚拟化栈相比一个浏览器是一个微不足道的表面,而它 所解析的东西(vCPU 寄存器状态、paravirt hypercall、 virtio buffer 描述符)也不是由一个网页所安排的敌对字 节。这个边界上的漏洞确实存在。它们属于完全不同难度档 次,比浏览器漏洞稀有好几个数量级,而当它们出现时通常都 是被 hypervisor 厂商带着一个 CVE 一起公布出来的。

这对用户意味着什么改变

具体到上周公开的这个 service-worker 漏洞,Bromure 用户 不需要等补丁。这个漏洞在他们的浏览器里同样存在——Bromure 跟随 Chromium 上游发布——但漏洞所启用的那种行为,恰好就 是 Bromure 的架构已经禁用了的那种行为。一个「永远活下 去」的 worker,至多只能活到它被注册在其中的那台 guest VM 的寿命,也就是至多和你保留的那个配置文件一样长,至少 和那个标签页开着的时间一样长。

在更一般的「未来某个月里会落下一个浏览器零日」的情形 下,Bromure 用户得到的结果形状不同。在最坏情况下,漏洞 利用所开火的对象是 guest 的内容:浏览器会话,那个配置文 件里当前已登录的站点 cookie,用户在那个浏览器里输入过的 任何东西。这是真实的损失,我们不打算把它藏起来。但没有 丢的是宿主:没有 SSH 密钥,没有 ~/.aws,没有钥匙串, 没有文稿,没有源码树,没有 LLM agent 工作区。

那个权衡——浏览器会话可以被攻陷,宿主不能,而且会话本 身是短暂的——是随着新浏览器漏洞出现的速率持续上升、越 来越说得通的那一种权衡。

我们不主张的东西

两个坦白说出来的注脚:

可丢弃的 VM 并不给 Chromium 打补丁

这个 service-worker 漏洞是一个 Chromium 漏洞。该修它 的是 Chromium 工程师,他们也理应去修。Bromure 的架构 改变的是这个漏洞还没被修复时所付出的代价;它不是修复 本身的替代品。我们发现东西时会上游报告,我们读的也是 其他所有人都在读的那些同样的安全公告。

VM 逃逸是一个真实存在的类别

hypervisor 本身的漏洞——Apple 的 Virtualization 框 架,或者它周围那些更小的表面之一——在原则上确实可以让 攻击者够到宿主。这些漏洞存在。但它们也比浏览器内存漏 洞稀有好几个数量级,表面也戏剧性地更小。Bromure 让宿 主对一次渲染器攻陷的暴露,依赖于_那_一类漏洞,而不是 依赖于每隔几周就出货的那一类漏洞。

你下个月要读的那个头条

会再出一个 Chromium service-worker 漏洞。会再出一个 V8 类型混淆。会再出一个 libwebp 堆溢出。会再出一个零日, 某个资源极其充裕的对手在用它对极其具体的一群人施加了极 其长的时间,然后在某个周二早上跟着一份紧急补丁公告一起 公之于众。

在那些清晨之中的任何一个,有用的问题不是你是不是已经重 启过浏览器了。有用的问题是:漏洞所落下的那个盒子里头实 际上装着什么。对今天的大多数用户而言,那个盒子就是装着 他们文件、钥匙串、已保存登录信息的那个盒子。对一个 Bromure 用户而言,那个盒子是一台 guest 虚拟机,它的最坏 损失也就是他本来反正就要关掉的那个窗口。

持久化需要有某个东西可以持久化于其上。让那个东西变得可 丢弃,整整一类攻击就不再是一类攻击。