那個不肯死的 service worker,以及那台毫不在乎的虛擬機
上週 Google 不小心重新公開了一個四年前的 Chromium 臭蟲——一個在瀏覽器關閉之後仍會繼續執行 JavaScript 的 service worker,在每一款主流 Chromium 瀏覽器上都還沒被修補。概念驗證程式碼已經流到外面。有意思的問題不是它怎麼運作。有意思的問題是:當一個瀏覽器底下那整台機器,會在你關掉分頁時整個消失,所謂的「持久化」到底還算什麼。
持久化不是程式碼的屬性。它是程式碼被允許賴以為生的那個地方 的屬性。當一個「永遠不死」的 service worker,所安裝的那台機 器在你每次關掉視窗時就被摧毀,它就有麻煩了。
在 2026 年 5 月 20 日,Chromium 的議題追蹤系統悄悄地撤除了一個 自 2022 年起就處於受限狀態的臭蟲存取限制。幾個小時之內,這個臭蟲 ——連同一份可運作的概念驗證——就在網路上被各處鏡像、封存與整 理。Google 隨即動手再把這個議題關回去。已經太遲了;那個頁面已經 進到了 Wayback Machine 裡,而 BleepingComputer 的整理文章 也在隔天上線。
這個臭蟲是真的,概念驗證可以動,而在它最初被回報的四年之後,它 在 Chrome Dev 150 和 Edge 148 上仍然沒被修補。它影響每一款 Chromium 衍生的瀏覽器:Chrome、Edge、Brave、Opera、Vivaldi、Arc。
它讓一個頁面可以做的事,用一句話講起來其實很平淡:註冊一個 service worker——一小段瀏覽器同意讓它在背景持續執行的 JavaScript——而它在頁面關閉、分頁關閉、瀏覽器關閉、甚至機器重 開機之後都不會停下來。最初在 2022 年 12 月回報這個臭蟲的研究員 Lyra Rebane,向 BleepingComputer 概括了最糟的情況:在 Microsoft Edge 上, 下載選單甚至不會出現, 所以結果就是「完全靜默的 JS RCE,在你關掉瀏覽器之後還繼續跑」。
這個臭蟲為什麼存在的技術細節,比起它使能的那一類攻擊要次要得 多。它是一塊持久化原語。一個你曾經造訪過一次的頁面,可能是好幾 個月前,可能是一則你早不記得看過的廣告,被允許無限期地在你的電 腦上繼續執行程式碼。殭屍網路徵召、廣告詐欺、加密貨幣挖礦、DDoS 參與、慢速資料外洩、每當你再次登入同一個網站時的順手撈取憑證 ——這一切,一旦你拿到一個能熬過瀏覽器關閉的據點,就全部變得不 費吹灰之力。
這是一個好的臭蟲,從具有啟發性這個意義上來說。這是一個壞的臭 蟲,從它會被用上這個意義上來說。
service worker 究竟是什麼
過去大約十年裡,網頁平台允許頁面安裝一種叫做 service worker 的 小型 JavaScript 程式。它們在背景執行,獨立於任何分頁之外,存在 的目的是讓網頁應用程式能做一些合理的事——把資源快取起來好讓網 站在你離線時還能載入、遞送推播通知、在網路恢復時同步資料。
一個 service worker 是針對某個 origin 註冊的(譬如說,https、
主機名 example.com、以及預設連接埠這個組合)。一旦註冊,瀏覽
器就會把它存到磁碟上,並在你下一次造訪該 origin 上任何東西時把
它帶回來——或者,在像是推播訊息這類背景功能的情形下,自行週期
性地把它喚醒。從使用者的角度看,這個 worker 是看不見的。從作業
系統的角度看,這個 worker 是瀏覽器的一部分,以瀏覽器行程所擁有
的任何權限執行。
Rebane 回報的這個臭蟲,大致上是說:一個惡意頁面可以濫用一個背景 下載 API,去註冊一個瀏覽器後來不肯終止的 worker。同一系列的其 他 Chromium service-worker 臭蟲過去也出過貨;這至少是過去十二 個月裡的第二件。這個樣式——頁面種下某個東西,瀏覽器把它存起 來,瀏覽器之後再自行把它拿回來——才是值得記住的部分。它也是 那種一旦瀏覽器不再擁有任何長期留存的磁碟時,就會撞上一面牆的 樣式。
持久化所需要的
持久化——在資安意義上,而不是網頁平台意義上——是攻擊者在拿到 初始存取之後立刻會做的那個動作。一個頁面或一份二進位執行檔落到 你的機器上之後,在做任何其他有意思的事情之前,會先安排自己的某 個部分去熬過顯而易見的清理步驟:關掉分頁、退出瀏覽器、重新開 機、回家的列車上把筆電闔起來。在桌面作業系統上,持久化有它自 己的一套詞彙——macOS 上的 launch agent、Windows 上的排程工作、 Linux 上的 systemd 使用者 unit、瀏覽器擴充功能、開機自啟動項。
一個 service worker 實際上,就是瀏覽器內的一個 launch agent。它 被註冊、被儲存、再依照瀏覽器掌控的時程被帶回來活過來。它是網頁 平台所提供的、比較優雅的那批持久化原語之一,這也是它一直在吸引 臭蟲上門的原因之一。
關鍵的是,上面那份清單裡每一種持久化形式,都有同一個要求,可以
用三個字寫出來:得有東西在那裡留下來。一個 launch agent 之
所以能持久化,是因為有一個檔案系統可以讓那個 agent 的 .plist
檔被寫上去,並且在每次開機時從那個檔案系統把它讀回來。一個
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 也只是一個磁碟卷快照,使用者隨時可以 回滾。
在可拋棄式的場景裡,那個「永遠不死」的 worker 沒有可以「不死」 的地方。下一次瀏覽器啟動時本來會把它讀回來的那一列磁碟資料, 是放在一台已經不存在的虛擬機裡。「在瀏覽器關閉之後還繼續跑」 的 JavaScript,之所以還能繼續跑,是因為有某個東西可以讓它繼續 跑。當瀏覽器的關閉同時也會關掉底下的機器,這個 worker 就會和 瀏覽器在同一時刻停下來——這正好是原本規格所預設的行為。
在設定檔的場景裡——譬如說,你保留了一個長期存在的「社群媒體」 設定檔,它本來就_應該_在不同工作階段之間記得你——這個 worker 會像它一直以來那樣存活,但只在那一個設定檔的 VM 範圍內。它仍然 看不到你的「文件」、你的鑰匙圈、你的其他設定檔,或你電腦上剩下 的任何東西。而如果你哪一刻懷疑那個設定檔沾上了什麼不該沾的東西, 你可以把整個 VM 回滾到它最後一個乾淨的快照,所花的時間大概就和 重新開一個瀏覽器差不多。
一般性的論點
把上面這些讀成「Bromure 剛好可以擋下這一個特定的 service-worker 臭蟲」,會是很容易的事。這是真的,但那不是有意思的主張。有意思 的主張更為一般,而這次事件只是它的一小塊證據。
瀏覽器零時差會繼續落下來。這至少是過去十二個月以來的第二件 Chromium service-worker 臭蟲。 2024 和 2025 年那些大件的 是 V8 型別混淆和 Mojo 沙箱逃逸,付出六位數價錢買來,由商業間諜 軟體廠商和國家對齊的團體所使用。在 service worker 出現之前就有 過 renderer 妥協;在這個特定臭蟲被補上之後,還會有 renderer 妥 協。要找到新的,那筆經濟學——尤其在Mythos 等級的 AI 稽核者 如今已經站到揭露窗口的兩邊——正在朝對防守方不利的方向移動, 而不是朝對他們有利的方向。
當一個零時差開火的時候,相關的問題不是_瀏覽器本身有沒有瑕疵_。 它就是有。相關的問題是_這個漏洞利用實際碰到了什麼_。
一個 Chromium 沙箱逃逸——也就是過去十年每隔幾個月你就會讀到一 次的那種典型「瀏覽器零時差」——是一條鏈,它拿到 renderer 某個 部分(V8、Skia、WebP、Dawn、libxml2)裡的一個記憶體臭蟲,並用它 去說服瀏覽器的 broker 行程做某件 renderer 本來不該能做到的事。 這些臭蟲之所以付得起六位數的價錢,是因為它們跨過了 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 的內容:瀏覽器工作階段、那個設定檔裡
目前已登入網站的 cookies、使用者在那個瀏覽器裡輸入過的任何東西。
那是一筆真實的損失,我們也不想把它藏起來。但沒有失去的是主機:
沒有 SSH 金鑰、沒有 ~/.aws、沒有鑰匙圈、沒有「文件」、沒有原
始碼樹、沒有 LLM 代理人的工作空間。
那個交換條件——瀏覽器工作階段可能被攻陷、主機不會、而且這個 工作階段本身是短的——就是在新瀏覽器臭蟲速率往上跑時,依然不 斷站得住腳的那個交換條件。
我們不會聲稱什麼
兩個直白的但書:
可拋棄式 VM 並不修補 Chromium
那個 service-worker 臭蟲是一個 Chromium 臭蟲。應該去把它修 好的人是 Chromium 工程師,他們也應該去修。Bromure 的架構改 變的是「這個臭蟲還沒被修」的代價;它不能取代修這件事本身。 當我們發現東西的時候會往上游回報,我們也讀和大家一樣的安全 公告。
VM 逃逸是一個真實存在的類別
hypervisor 本身的一個臭蟲——Apple 的 Virtualization 框架、或 它周圍那些較小面積裡的某一個——原則上會讓攻擊者得以抵達主 機。這些臭蟲存在。它們也比瀏覽器記憶體臭蟲在數量級上要稀有 得多,而且攻擊面要小得多。Bromure 讓主機暴露於 renderer 妥 協這件事,取決於_那_一類臭蟲,而不是每幾週就出貨一次的那一 類臭蟲。
下個月你會讀到的那則標題
會再有一個 Chromium service-worker 臭蟲。會再有一個 V8 型別混
淆。會再有一個 libwebp 堆積溢位。會再有一個零時差,被某個資
源極為充沛的對手,長時間用在極為特定的人身上極為長的時間,在某
個禮拜二的早上隨著一份緊急修補公告而公開。
在那當中任何一天的早上,有用的問題並不是你是不是已經重新啟動了 瀏覽器。有用的問題是:那個臭蟲落進去的那個盒子裡頭,實際上裝著 什麼。對今天大多數使用者而言,那個盒子就是裝著他們檔案、鑰匙圈 和已儲存登入資訊的那同一個盒子。對一個 Bromure 使用者而言,那個 盒子是一台 guest 虛擬機,它最壞的損失就是他們本來反正就要關掉的 那扇視窗。
持久化需要某個東西可以留下來。把那個東西做成可拋棄的,整整一類 攻擊就不再是一類攻擊。