vatt'ghern jaskier's ballads

2026 年 10 月 19 日 UTC 約 17:00,Cloudflare 得知八張 eMudhra 中繼憑證的私鑰已經外洩,攻擊者可以簽出任何網域的憑證。最後一批緩解在隔天 19:20 UTC 部署完畢;到了 10 月 28 日,macOS 上的 git、curl 和 python3 仍然信任這些憑證。

eMudhra 中繼憑證外洩,誰先封、誰還沒

這篇照時間順序走一遍:各家信任庫在哪一天封鎖了這些憑證,Cloudflare 在知情之後的二十六小時裡先動了哪些地方,修補發佈以後客戶手上還有哪些信任庫要自己檢查,以及落在最後面的 macOS 命令列工具。資料來自三個頁面:Cloudflare 的事件文章,Dennis Jackson 發佈、社群共同維護的 eMudhra 追蹤頁,以及追蹤頁上專談 Apple 的子頁。兩邊的立場不一樣。Cloudflare 講的是自己做了什麼、客戶該做什麼;追蹤頁在點名還沒修的廠商。下面每一處引文都會標明出自哪一邊,推斷的地方也會明說是推斷。

兩條時鐘:十七日通報,十九日才輪到下游

先把日期對齊,因為兩份資料用的起點不同。追蹤頁寫道:「reported it to the Root Program Teams on the 17th of October.」又寫:「The issue was responsibly disclosed to downstream trust stores on the 19th of October.」Cloudflare 的時間表則從 10 月 19 日約 17:00 UTC 起算:「Cloudflare becomes aware of the compromise and begins assessment.」兩個日期差兩天。合理的推測是 Cloudflare 屬於下游,收到的是 19 日那一輪通知,不過 Cloudflare 的文章沒有寫它是怎麼得知的,這一點只是推斷。對讀者而言,重點是這件事從一開始就有兩條時鐘在走:一條屬於 root program 團隊,另一條屬於所有靠這些團隊轉手拿到信任名單的下游。

出事的是什麼,兩邊的描述正好咬合。Cloudflare 的說法是:「private keys associated with eight eMudhra intermediate certificates were compromised.」後果是「an attacker can create a forged certificate for any domain」。追蹤頁交代了來由:「The private keys for the intermediate certificates were incorrectly generated by the CA.」所以「anyone can recover them from the certificates.」照字面讀,問題出在金鑰產生的階段,而不是金鑰事後被人偷走;至於具體為什麼能從憑證推回私鑰,這幾份資料沒有講到演算法層面的細節,我也不補。

這八張憑證的分量來自它們掛在誰底下。追蹤頁說它們「chain to roots trusted by the Mozilla, Microsoft, Apple and Chrome root programs」,四個主要信任計畫全在其中。CA 這一端的動作已經完成:「The CA has also revoked the compromised intermediate certificates.」但撤銷只是 CA 端的記號,每一個信任庫何時採用這個記號、用什麼機制採用,要看各家自己的更新管道。下面這張圖就是把這件事按日期攤開,拖動滑桿可以停在任何一天。

拖動日期,看各方在 10 月 17 日到 28 日的封鎖狀態 · 5 個對象 × 12 天

17 日 通報 root program 團隊 19 日 下游 trust store 收到通知 17181920 21222324 25262728 UTC 日期(10 月) Firefox 與 Chrome Cloudflare 用戶端與 CDN Microsoft 平台 Apple 產品 macOS 命令列工具 自 17 日起封鎖 自 20 日起封鎖 自 20 日起封鎖 自 22 日起封鎖 仍信任
UTC 10 月 28 日:5 個對象中 4 個已封鎖
封鎖日期取自追蹤頁的「since」說法,Cloudflare 一列取自它自己的時間表(各項在 20 日 02:00 到 19:20 UTC 之間完成);「尚未封鎖」是由這些日期反推,不是各家公告。macOS 命令列工具一列的依據是追蹤頁 Apple 子頁 10 月 28 日的狀態。

追蹤頁給的封鎖日期是這樣寫的:「The certificates have been blocked in Firefox and Chrome since the 17th of October,」接著是「on Microsoft platforms since the 20th and on Apple products since the 22nd.」以 17 日通報為起點,Firefox 與 Chrome 是當天,Microsoft 是第 3 天,Apple 產品是第 5 天,天數是我用日期相減算的。把滑桿拉到 19 日,也就是下游收到通知、Cloudflare 剛開始評估的那一天,依這些日期推算,已經封鎖的只有兩家瀏覽器,其他人都還信任這八張憑證。Cloudflare 自己的服務要到 20 日才完成,下一節細看。圖最下面那一列是這個故事後半段的主角:macOS 的命令列工具,到 28 日仍然沒有封鎖。

為什麼兩家瀏覽器能領先這麼多?Cloudflare 文章裡有一句可以當線索:「most browsers use quick-revocation mechanisms (like Chrome CRLSets)」。瀏覽器可以不等整個作業系統更新,就把撤銷資訊直接推給使用者。合理的推測是,這類機制讓 Firefox 與 Chrome 在通報當天就能封鎖,但兩份資料都沒有把這兩件事明確連在一起,這條因果是推斷。可以確定的是封鎖日期的先後:機制輕的先動,機制重的後動,而沒有任何撤銷通道的檔案式信任庫,是最後一個。

讀這張圖時有幾個限制要記住。第一,封鎖日期全部來自追蹤頁一家的說法,Cloudflare 的文章沒有逐家列出別人的日期,所以這幾個日子目前是單一來源,沒有第二份獨立資料可以對照。第二,「封鎖」在不同平台上不一定是同一個動作:追蹤頁對瀏覽器、Microsoft 和 Apple 用的都是 blocked 這個字,Cloudflare 對自己的服務用的卻是 updates 與 mitigations,圖上把它們放在同一條時間軸,是為了比較先後,不代表背後的做法相同。第三,「尚未封鎖」是我從「自某日起」的說法反推的,沒有哪一家公告過「在某日之前還沒封鎖」。這三點都不影響先後順序的結論,但會影響你能把這張圖引用到多精確。

二十六小時:Cloudflare 先修哪一層

Cloudflare 對自己的處置順序是這樣描述的:「We immediately prioritized patching critical services across Cloudflare to protect our customers」。「critical services」指哪些,文章的摘要版本列了兩類:連到客戶 origin 的 proxy,以及呼叫外部 API 的應用,我沒有逐字核對到這一句。如果照這兩類讀,合理的推測是它們都是 Cloudflare 主動發起 TLS 連線、必須自己驗證對方憑證的地方,信任庫若不更新,偽造的憑證就能被當真,這是我的推論。文章把後續動作整理成四個時間點,全部以 UTC 計。

離 10 月 19 日約 17:00 UTC 的經過時間(起點是「約」,數字皆為約略值) 10 月 19 日 約 17:00 Cloudflare 得知, 開始評估 10 月 20 日 02:00 Cloudflare One 用戶端與 WARP 更新 +9 小時 20 日 09:15 cloudflared 更新發佈 +16 小時 15 分 20 日 19:20 CDN 與伺服端 Zero Trust 緩解全數部署 +26 小時 20 分 三段間隔:9 小時,7 小時 15 分,10 小時 5 分
四個時間點取自 Cloudflare 文章的時間表;「+」後面的經過時間是我用時間相減算的。

順序本身有資訊。最先出的是裝在使用者機器上的用戶端:「Cloudflare One Client and WARP app security updates published across platforms.」其次是 Tunnel 的 cloudflared:「Cloudflare Tunnel (cloudflared) security update published.」最後才是 CDN 與伺服端:「CDN and server-side Zero Trust mitigations fully deployed.」合理的推測是,需要使用者自己升級的軟體必須先發佈,才有時間讓使用者去升,而 Cloudflare 自己控制的伺服端可以在後面一次部署完畢。文章沒有解釋排序的理由,這是我的讀法。比較有意思的是第二段和第三段的長度:cloudflared 之後又過了 10 小時 5 分,伺服端才全數到位,是三段間隔裡最長的一段,文章沒有說明這段時間花在哪裡。

對 Zero Trust 客戶,Cloudflare 給了一條最短的路:「If you are using Cloudflare Zero Trust with Gateway and an up-to-date client (see below), you are automatically protected.」這句話有兩個條件,一是用了 Gateway,二是用戶端是最新的,缺一不可,所以用戶端更新排在第一個發佈,合理的推測是出於這個依賴,不過這也是推斷。至於有沒有人已經被利用,文章的結論是:「Our security review has not identified active exploitation of this issue on Cloudflare services.」這個範圍只到 Cloudflare 的服務。這句話不能讀成整個網路上都沒有人利用,文章也沒有這麼說。

「automatically protected」這個說法也值得拆開讀。它的前提是客戶已經在用 Zero Trust 與 Gateway,而且用戶端是最新的;文章沒有說只用 CDN 的客戶、只跑 cloudflared 的客戶或沒有裝用戶端的客戶,是否也自動受保護,那些人要回頭看文章後面的客戶建議。所以同一句話對不同客戶的意義差很多:對符合條件的人,它代表什麼都不必做;對不符合條件的人,它只代表 Cloudflare 的邊緣部分已經處理,剩下的仍要自己核對。把「已部署緩解」讀成「與我無關」,是這類公告最常見的誤讀方式。

把兩條時鐘接起來看,會得到一個對客戶更實際的數字。通報在 10 月 17 日,Cloudflare 最後一批緩解完成在 20 日 19:20 UTC,中間隔了三天多。按文章的時間表,Cloudflare 自己的工作是從 19 日約 17:00 才開始的,前面那兩天它是否已經知情,文章沒有寫,所以這裡不能替它補。換個角度看,同一段時間裡每個人面對的暴露窗口並不一樣:只用 Firefox 或 Chrome 的使用者,窗口幾乎從頭就關了;靠 Cloudflare 伺服端緩解的客戶,窗口關在 20 日晚間;靠自己的用戶端、信任庫或命令列工具的人,窗口要等他們自己動手才會關。這是我從幾組日期對照出來的推論,沒有任何一份來源用「暴露窗口」這個說法。

修完之後還剩什麼:握著自己信任庫的那幾條路

Cloudflare 部署完緩解之後,文章把剩下的工作交還給客戶。這些工作的共同點是:信任庫在你手上,不在 Cloudflare 的邊緣。下圖把它們畫在請求路徑上。鼠尾草綠標的是 Cloudflare 已經發佈或部署的部分,赤陶色標的是要你自己動手的部分。上排是請求的主線,從用戶端經過邊緣到 origin;下排的三個地方不在主線上,卻同樣各自握著一份信任庫。

請求主線(用戶端到 origin) Cloudflare One 與 WARP 用戶端與 WARP app 更新已發佈 你要做:升級 Cloudflare 邊緣 CDN、Zero Trust 緩解已部署 你要做:查 mTLS 信任庫 你的 origin Full (strict) 或 Strict 自訂 origin 憑證 你要做:查憑證與設定 不在主線上,同樣各握一份信任庫 cloudflared Cloudflare Tunnel 更新已發佈 你要做:升級或換映像 容器與相依套件 映像裡的信任庫 可能是間接相依 你要做:查映像與依賴 應用自己的驗證器 SDK、執行環境、作業系統 誰在驗證要實際追 你要做:走實際路徑測 鼠尾草綠:Cloudflare 已發佈或已部署 赤陶色:需要你自己動手
各格內容整理自 Cloudflare 文章的客戶建議;方框的位置關係是示意,不代表實際連線拓樸。

逐項看。第一項是 mTLS:「Review and update your mutual TLS trust store/uploaded CAs used to verify client certificates」。mTLS 驗證的是客戶端憑證,信任哪些 CA 由客戶上傳決定,合理的推測是這正是 Cloudflare 的邊緣緩解動不到的地方,只能請客戶自己改;文章沒有明說這個理由。第二項是 origin:若用 Full (strict) 或 Strict 模式,文章要客戶「Review your custom origin certificates and server configurations if using」這些模式。這裡同樣是客戶選擇的憑證與設定,Cloudflare 只能提醒。

第三項最容易漏,因為它藏在映像和相依套件裡。文章的範圍是:「Customers using Cloudflare Containers, including Sandboxes, should review their container images」,也就是用 Cloudflare Containers(含 Sandboxes)的客戶要檢查容器映像;文章的摘要版本還提到要一併檢查應用相依的信任庫,這一半我沒有逐字核對。對這類服務,文章另外提到可以啟用 HTTPS 出口攔截(HTTPS egress interception)作為選項。第四項是驗證路徑本身。文章要開發者檢查實際走的 TLS 路徑,並提醒受影響的套件可能不在你直接寫的程式碼裡:「Note that some of these may be indirect dependencies of your software stack, so look at the dependency tree.」測試也有陷阱:「Neither does a curl test if the application uses a different verifier.」前半句講的是瀏覽器測試不能證明背景服務受到保護,後半句說 curl 也一樣,只要應用用的是另一個驗證器。

還有一條不是做什麼,而是不要做什麼:「It's important to not resolve these errors by disabling verification.」更新信任庫以後,原本連得上的服務可能開始報憑證錯誤,最快的消除辦法是關掉驗證,而文章警告這會把修補帶來的保護一起拿掉。換句話說,錯誤訊息在這個時間點可能是修補生效的證據,不一定是新的故障。這是我對文章論點的整理,文章原話只有上面那一句。下表把這幾項按角色排開,右欄是各項的原文依據。

按角色整理的檢查清單。第三欄是原文,前兩欄是我的歸類。
你是誰要做的事原文依據
用 Cloudflare One 用戶端或 WARP app 的人 升級到修補版 「Cloudflare One Client and WARP app security updates published across platforms.」
跑 cloudflared 的人 升級;容器部署要重新部署修補後的映像 「Cloudflare Tunnel (cloudflared) security update published.」
用 mTLS 驗證客戶端憑證的人 檢視並更新 mTLS 信任庫與上傳的 CA 「Review and update your mutual TLS trust store/uploaded CAs used to verify client certificates」
用 Full (strict) 或 Strict 的人 檢查自訂 origin 憑證與伺服器設定 「Review your custom origin certificates and server configurations if using」
用 Cloudflare Containers(含 Sandboxes)的人 檢查容器映像 「Customers using Cloudflare Containers, including Sandboxes, should review their container images」
寫應用的開發者 沿相依樹追實際的驗證器;不要用關掉驗證來消除錯誤 「It's important to not resolve these errors by disabling verification.」
在 macOS 上用 git 的開發者 跑檢查指令;每次系統更新後重做 「A certificate error means your git is protected.」

如果只能排一個順序,下面是我的排法,來源並沒有替讀者排過。先升級用戶端和 cloudflared,因為修補版已經發佈,這是成本最低、也最確定的一步。接著查 mTLS 信任庫和 Full (strict) 的 origin 憑證,因為這兩處是客戶自己選的,沒有人能代勞,查的時間也最短。再來才是容器映像和相依樹,這一步最花工夫,因為受影響的套件可能是間接相依,文章自己都提醒要沿著相依樹往下看。最後是驗證:用應用實際走的路徑測一次,而不是只用瀏覽器或 curl 看一眼。這個順序的邏輯是「現成的修補先吃掉,只有你能改的再查,最費工的最後做,做完用真路徑驗收」。

上游的修補也在往下擴散。Cloudflare 的文章提到,Mozilla 的 Dennis Jackson「has crowdsourced a list of commonly used packages that have been patched」。我讀取追蹤頁時,「Who has patched?」一節按類別列出已修版本,例如 Node.js 22.23.4、24.22.1 與 26.12.1,NSS 3.131.1,以及日期為 2026-10-20 的 curl CA bundle;同一節也有標成 pending 的項目,包括 certbot、containerd、Godot、LibreOffice、Java/OpenJDK、Bottlerocket 和 OpenHarmony。這份清單天天在變,上面只是我讀取當下的樣子,要以頁面現況為準。

落後的那一塊:macOS 命令列工具

Apple 子頁的第一句就是日期:「Apple was notified of the eMudhra incident on 17th October.」同一天,root program 團隊也收到通報。依照前面追蹤頁的說法,Apple 產品從 22 日起封鎖,也就是通報後第五天;可是同一頁在 10 月 28 日的狀態是,macOS 的開發者工具,包括 git、curl 與 python3,「still trust the compromised certificates.」這兩個說法並不矛盾,Apple 子頁自己交代了原因:「Apple has already protected Safari and other apps built on its networking framework.」換句話說,被保護的是走 Apple 網路框架的那一半。

同一台 macOS 同一條被攔截的 HTTPS 連線 Apple 網路與安全框架 Safari、走此框架的 app 系統檔 /etc/ssl/cert.pem git、curl、python3 據此驗證 封鎖 Apple 產品自 22 日起 仍信任 截至 10 月 28 日 Apple 的說法:在它發佈自己的修補前,macOS 更新可能換掉該檔案
左側同一條連線,兩條驗證路徑得到相反的結果。系統檔路徑與「更新可能換掉」的說法取自追蹤頁的 macOS 說明與 Apple 指引。

這就是前面那句「curl 測試也不夠」在 macOS 上最具體的版本。同一台機器上有兩個驗證器,一個吃得到 Apple 的封鎖,另一個吃不到。追蹤頁的 macOS 說明寫:「macOS blocks the compromised intermediates in apps that use Apple's networking and security frameworks.」而內建的命令列工具是「the built-in command line tools verify certificates against」系統檔 /etc/ssl/cert.pem。你在 Safari 打開測試站看到警告,不代表終端機裡的 git 看得到同一個警告。

風險是追蹤頁的描述,語氣很直接:「Anyone can intercept any HTTPS connection」,指的是這些工具發出的連線。對 git 來說,clone 與 pull 可能被送進攻擊者選擇的程式碼,頁面的原話是「can deliver attacker-chosen code.」頁面也提到推送可能被竄改、GitHub token 之類的憑證可能被攔走。這是追蹤頁對風險的陳述;我讀到的內容沒有提到已經有人實際利用,所以這裡不能寫成已經發生。檢查方法很短,頁面給的是:

git ls-remote https://git-emudhra.we-have-a-problem.com/check.git
curl https://dv-g3b-noct-emudhra.we-have-a-problem.com/
curl https://dv-g3b-aaa-noct-emudhra.we-have-a-problem.com/

判讀規則在頁面上這樣寫:「A certificate error means your git is protected.」出現憑證錯誤代表受到保護;反過來沒有出錯、連得上,就表示沒有被擋住,這是我順著這句話讀出來的,另外兩條 curl 檢查是否同樣判讀,我沒有逐字核對。這跟前面「不要為了消除錯誤而關掉驗證」是同一件事的兩面:在這份檢查裡,憑證錯誤正是你要看到的結果。

最麻煩的部分在後面。追蹤頁 10 月 28 日的更新寫:「macOS 27.2 undid user-applied fixes.」Apple 子頁的說法更完整:這次更新「did not fix the issue and undid the workaround that users had applied by hand.」手動的做法,追蹤頁首頁有寫:改系統檔 /etc/ssl/cert.pem,移除 emSign ECC Root CA - G3 那一筆。光做這一步擋不住六張 Sectigo 交叉簽發的中繼憑證,它們鏈到 AAA Certificate Services;首頁因此另給一個選項,連 AAA Certificate Services 根憑證一併移除,代價是「may break verification of unaffected websites」。所以修法本身有取捨,不只是照做。因為更新會把檔案換回去,Apple 子頁的結論是:「developers need to repeat the checks above and the workaround after every update until Apple ships a fix.」

對 Apple 的要求,頁面只寫了一句:「Apple needs to ship a fix for macOS developers as soon as possible.」對安全團隊,頁面建議評估 macOS CI runner 和建置基礎設施是否帶有供應鏈風險。這個建議值得多想一步:開發者個人的筆電上,攔截需要攻擊者位在網路路徑上;CI runner 通常每次都從外部拉依賴、拉原始碼,被攔的機會每天重複發生。這一步是我的延伸推論,頁面只說了要評估。

Cloudflare 的文章和 Apple 子頁是兩家不同立場的來源,卻在同一個地方收斂:要驗證的是實際執行驗證的那個元件。Cloudflare 說:「Neither does a curl test if the application uses a different verifier.」Apple 子頁則把同一件事落在一台機器上,Safari 看到的是封鎖,git 看到的是一條可以連上的連線。兩邊都沒有叫讀者相信某個版本號就算完成,而是叫讀者自己跑一次檢查。對維運的人來說,這比任何一份公告都實用:公告只能告訴你廠商做了什麼,檢查指令才能告訴你你的機器現在是什麼狀態。

把整個過程放在一起,有一個規律可以記下來,但它是推斷,不是來源的結論:封鎖速度跟信任庫能不能獨立於系統更新而被撤銷有關。Chrome CRLSets 這類機制在通報當天就能動;Microsoft 在第三天,Apple 框架內的產品在第五天;最後一個是把信任名單存成一個檔案、更新時整份覆蓋的地方。Cloudflare 文章那句「most browsers use quick-revocation mechanisms (like Chrome CRLSets)」,指的大致是同一個方向,不過它是否由此主張信任庫都該有更快的更新管道,我沒有逐字核對,這裡不替它下結論。這件事在 10 月 28 日還沒有結束:追蹤頁上還有標成 pending 的項目,Apple 命令列工具的修補也還沒有發佈。

Still open:Cloudflare 的緩解在知情後約二十六小時內完成,但你自己的信任庫沒有人替你更新——先升級用戶端,再查 mTLS、origin 憑證和容器映像,最後在 macOS 上跑一次 git ls-remote,並且在每次系統更新之後再跑一次。