瀏覽器能擋住一條不可信憑證鏈,不代表同一台機器上的 curl、Python job 或 Go 服務也會擋住。這次 eMudhra 事件,我會把「每個實際 TLS client 已拒絕受影響鏈」當成結案條件;只登記作業系統已更新,還無法確認服務正在使用哪一份信任資料。

Mozilla 事件紀錄 2083449確認八張 eMudhra 中繼 CA 憑證的私鑰遭破壞:六張用於 TLS,兩張用於程式碼簽署。Mozilla 驗證了通報者的控制證明,也獨立還原了受影響私鑰。紀錄把問題指向這批金鑰的生成方式,跟網站憑證過期無關,也沒有證據顯示橢圓曲線演算法整體失效。

10 月 20 日新增的是修復傳遞,事故起點在 10 月 17 日

Mozilla 在 10 月 17 日收到通報並處理 Firefox 封鎖,eMudhra 在 10 月 18 日將八張受影響憑證標為撤銷。10 月 20 日公開的跨平台說明頁把各平台的處理與下游更新整理在一起,一般開發團隊可以從這裡開始核對自己的信任路徑。

到 2026 年 10 月 21 日我查核時,公開資料還沒有證據顯示非瀏覽器 client 已遭在野濫用,但這只代表目前沒看到。我關心的是降低後續的信任風險,不由此推定讀者環境已被入侵,也不建議在沒有證據時輪換所有應用程式祕密。影響範圍與已知原因見事件說明。

CA 發出撤銷訊息,和你的程式實際使用該訊息,是兩件事。對會接收平台封鎖資料的 client,更新可能經由瀏覽器或系統機制到達。對靜態信任包、內嵌驗證器或特殊 runtime,可能需要套件更新、重新編譯或重建映像。修復方式要從執行路徑往回查,不能只從主機名稱推定。

一份清單要能指到正在跑的程序

我會先將對外 TLS 呼叫按部署單位列出,尤其是 CI runner、排程工作、下載器、套件管理工具及長期存活的服務。下面是我建議的盤點欄位,是一份範本,不是已經做完的稽核:

  • 身分:服務名稱、負責人、執行檔路徑與容器 image digest。
  • 驗證器:TLS backend、runtime 版本,以及程式有沒有自訂憑證驗證。
  • 信任來源:系統信任庫、套件附帶的 bundle、環境變數指定檔案,或應用程式自行載入的 CA。
  • 更新方式:套件更新、重建 binary、重建 image、重新部署或重新啟動;誰能確認新產物已替換舊程序。
  • 驗收:受影響鏈被拒絕、正常目的地仍成功,以及測試發生的確切產物與時間。

同一個 repository 可能同時有多條路徑。開發機的 Python 與 container 裡的 Python,甚至同一映像中兩個虛擬環境,都不能自動共用一張通過紀錄。盤點的最小單位要能對應到部署產物,不然重新部署時,沒有人知道那個綠燈驗的是哪一份程式。

Go 的修補說明了為什麼更新 CA 檔不一定夠

Go 團隊在 10 月 19 日的安全公告發布 1.27.3 與 1.26.10。crypto/x509 的 Verify 會過濾包含指定中繼憑證的鏈。公告編號為 CVE-2026-111943,並建議立即升級。這是驗證程式邏輯層的緩解。

對使用受影響 Go 驗證路徑的既有服務,更新開發機工具鏈不會自動改寫已經部署的 binary。要用修補過的工具鏈重建,確認映像或執行檔已發布,並讓流量進到新程序。若服務使用自訂驗證、外部 TLS proxy 或不同 backend,光憑 go version 不能結案,還要另外核對實際路徑。

Python 路徑則可能需要核對套件帶來的 bundle,事件整合頁列出 certifi 2026.10.19 發行。curl 的 Mozilla CA bundle 發布頁也可查到 10 月 20 日版本,但下載了新 bundle 之後,還要證明該程序確實載入了它。這些版本只是我查核時用來定位的線索。

CPython 10 月 24 日合併公鑰拒絕清單,交付版本要另外核對

Python 團隊在 10 月 24 日安全公告說明,ssl 模組加入已知受損憑證的拒絕清單,對應 CVE-2026-115557。PR 160042同日合併到 main。這是較早 eMudhra 事件的新緩解措施,事故日期沒有改變。

修補比對 DER 編碼的 SubjectPublicKeyInfo(SPKI)之 SHA-256 摘要。若不同交叉簽署憑證沿用同一把公鑰,就仍能被辨識。原始設計討論說明,這是信任庫更新有落差時的補強;移除信任的責任仍在各信任庫。討論也明列在啟用憑證驗證時執行檢查,不能把這次修補理解成允許應用程式停用驗證。

到 10 月 25 日查核時,主 PR 與 3.11 至 3.15 的回補紀錄已可見。原始碼分支有修補,還需要確認供應者何時交付套件、映像實際裝到哪一版,以及服務是否已重新啟動。我沒有據此核實任何穩定發行版或作業系統套件已包含修補,也沒有執行 CPython 的憑證回歸測試。

因此 Python 盤點多留一欄「供應者修補依據」,連到所用發行套件或映像的公告,再用原有正負向測試驗收實際程序。只把 PR 狀態抄成已修復,會漏掉部署交付這一步。

負向測試要證明拒絕的是憑證鏈

事件說明頁提供測試主機與離線憑證鏈,並區分原始鏈及交叉簽署鏈。驗收應涵蓋自己可能信任的路徑,不能只測一個 host 成功被擋,就宣布全部覆蓋。

下面是事件說明頁提供的其中兩個 curl 連線例子,我沒有在你的環境跑過。只在允許對外測試的隔離環境使用,不附加 token、cookie 或任何正式資料:

curl https://dv-g3b-emudhra.we-have-a-problem.com/
curl https://dv-g3b-aaa-emudhra.we-have-a-problem.com/

這兩條只示範兩種鏈。測試報告要保留 TLS 驗證錯誤及使用的 backend。DNS 失敗、逾時、出口被防火牆擋住,都只能記為未完成,不能當成憑證鏈已被拒絕。同時測一個組織核准的正常 TLS 目的地,避免把「網路壞了」誤記成「信任修好了」。

若正式應用經由企業 TLS interception proxy,外部測試可能只驗到 proxy 的憑證。此時需要分開確認應用到 proxy 與 proxy 到外部的驗證責任,或使用事件說明頁提供的離線鏈在相同驗證器中測試。測試工具和正式程式使用不同 backend 時,也要揭露這個覆蓋缺口。

不以 --insecure、停用 hostname 驗證或自行接受所有憑證換取連線成功。若修補使業務端點失敗,先保留鏈與錯誤證據,讓憑證持有人處理重發或鏈配置。擅自刪除廣泛信任的根憑證可能波及其他合法鏈。優先採用供應者的正式更新;需要緊急封鎖時,先經變更審查、限定範圍,並準備回滾。

結案時交出重建產物與正負向測試

我建議的結案包包含:供應者修復依據、實際 runtime 與信任來源、重建後的 image digest/binary 身分、部署完成證據,以及正負向連線測試。暫時沒有正式更新的元件,標明負責人、暴露路徑與緩解方案,不把「等待供應者」改成已完成。

兩張程式碼簽署中繼憑證還需要獨立追蹤簽章驗證與平台政策,TLS 連線測試不能替它們結案。這也是為什麼我不提供一條刪除根憑證的命令,假裝同時解決瀏覽器、後端服務與程式碼簽章。

站內的 TLS 憑證驗證除錯適合查連線失敗經過哪個驗證步驟。這次可以把方法往前推一步:選一個正在正式環境對外連線的程序,寫清楚它究竟信任誰、更新從哪裡進來,然後對那一份實際部署產物留下拒絕證據。