返回

2026/10/23

2026 年 10 月 20 日,一項消息公開:印度 CA eMudhra 所營運的八個中繼憑證授權單位(CA),其私鑰可被任何人還原。瀏覽器在問題通報當天就封鎖了這些憑證。然而,許多 Java 應用程式至今仍信任它們,而且在有人更新 JDK 或修改其信任庫之前,會一直信任下去。

2026 年 10 月 21 日,BellSoft 發布了測試結果,顯示許多 Java 執行環境仍接受這些憑證。本文整理了他們的發現(我們已在自己的測試中驗證),並說明為什麼瀏覽器不受影響而 Java 會受影響、如何檢查您的系統,以及該如何因應。

重點摘要

  • eMudhra 有八把中繼 CA 私鑰已外洩。任何持有其中一把私鑰的人,都能為任意主機名稱簽發憑證,而信任該憑證鏈的用戶端會接受它。
  • 瀏覽器已封鎖這些憑證,但 Java 使用自己的信任庫,而且預設既不檢查撤銷狀態,也不檢查憑證透明度(Certificate Transparency)。
  • 多家廠商的 JDK 建置版本仍信任部分或全部受影響的憑證鏈,容器映像檔中的 JDK 也不例外。
  • 在廠商推出修正版 JDK 之前,請在測試後從信任庫中移除這兩個受影響的根憑證。啟用撤銷檢查是第二種做法。

發生了什麼事

eMudhra 在產生其八個中繼 CA 憑證的私鑰時出了錯,導致任何人都能從公開的憑證還原出這些私鑰。自這些中繼憑證於 2025 年 4 月簽發以來,這個問題就一直存在。

2026 年 10 月 17 日,一名通報者用這八把私鑰分別對訊息「We have a problem here」進行簽章,並將簽章寄給各瀏覽器的根憑證計畫,藉此證明了這個問題。詳細資訊於 10 月 20 日公開。

  • 六個中繼憑證用於簽發 TLS 憑證:即 eMudhra 的 DV、OV 和 EV TLS CA,分為 G3B 與 G3C 兩代。
  • 兩個中繼憑證用於簽發程式碼簽章憑證。
  • 每個中繼憑證都有兩個版本:一個由 eMudhra 的 emSign 根憑證簽署,另一個由 Sectigo 的 AAA Certificate Services 根憑證交叉簽署。在 JDK 信任庫中,這些憑證鏈會終止於下列兩個根憑證之一:emSign ECC Root CA - G3(透過交叉簽署)或 AAA Certificate Services。

Firefox 和 Chrome 自 10 月 17 日起封鎖這些中繼憑證,Apple 自 10 月 19 日起,Microsoft 自 10 月 20 日起。eMudhra 已於 10 月 18 日和 19 日以 CA_COMPROMISE 為理由撤銷全部八個中繼憑證。各 Linux 發行版也已釋出更新的 CA 憑證套件。

有什麼影響

任何持有其中一把私鑰的人,都能為任意主機名稱建立憑證,例如您的銀行、您的雲端服務供應商,或您自己的 API 閘道。信任該憑證鏈的用戶端會接受這種憑證。若再搭配某種攔截流量的手段,例如遭入侵的網路或遭竄改的 DNS 記錄,攻擊者就能:

  • 讀取並竄改 Java 應用程式與其所呼叫服務之間的流量,包括密碼、API 金鑰和權杖;
  • 冒充套件儲存庫、更新伺服器或合作夥伴的 API;
  • 利用那兩個程式碼簽章中繼憑證為程式碼簽章,使其看起來像是來自受信任的發行者。

這些中繼憑證從未出現在憑證透明度(Certificate Transparency,CT)日誌中,因此強制執行 CT 的瀏覽器不可能透過它們遭到攻擊。目前還不清楚這些私鑰是否曾被用來攻擊不檢查 CT 的用戶端,而 Java 正是這類用戶端之一。

為什麼 Java 仍然信任這些憑證

Mozilla、Google、Apple 和 Microsoft 透過各自的管道(例如 OneCRL、CRLite 和 CRLSets)推送了封鎖措施,而 JDK 預設的 TLS 實作不會讀取其中任何一個。以下三項預設設定讓 Java 仍然暴露在風險中:

  • 自己的信任庫。除非應用程式設定了 javax.net.ssl.trustStore,否則 Java 會信任 $JAVA_HOME/lib/security/jssecacerts(若該檔案存在)中的根憑證,該檔案不存在時則信任 $JAVA_HOME/lib/security/cacerts 中的根憑證。廠商隨附的 cacerts 既不會收到瀏覽器的封鎖清單,也不會收到作業系統的 CA 更新。只有在管理員編輯它,或 JDK 更新將其取代時,它才會改變。
  • 不檢查撤銷狀態。除非您主動啟用,否則 Java 不會檢查撤銷狀態(com.sun.net.ssl.checkRevocation 預設為 false),因此在預設設定下,CA 的撤銷動作完全看不到。
  • 不檢查憑證透明度。JDK 不強制執行 CT,因此保護了瀏覽器使用者的那層機制在這裡並不適用。

揭露頁面列出了十二台測試主機,對應每一種中繼憑證與根憑證的組合。BellSoft 於 10 月 21 日在 Linux 上測試了這些主機,我們則於 10 月 23 日重複了這項測試:

測試的執行環境受信任的測試主機
Liberica JDK 21.0.1012 台中有 12 台
Liberica JDK 8u504(容器映像檔)12 台中有 12 台
Liberica JDK 21.0.12.1(容器映像檔)12 台中有 12 台
Eclipse Temurin 25.0.4.112 台中有 6 台(僅 eMudhra 根憑證)
Eclipse Temurin 21.0.12.1(容器映像檔,我們的測試)12 台中有 6 台(僅 eMudhra 根憑證)
Arch Linux OpenJDK 27(系統 CA 存放區)12 台中有 6 台(僅 eMudhra 根憑證)

差別在於 Sectigo 根憑證。Mozilla 先前已移除 AAA Certificate Services,而 Temurin 和 Arch Linux 的信任庫是依據 Mozilla 清單建置的,因此只有 eMudhra 這條路徑是開放的。Liberica JDK 的信任庫仍包含該根憑證,因此兩條路徑都是開放的。

需要檢查哪些系統

  • 獨立安裝的 JDK:從廠商封存檔解壓縮,或使用廠商安裝程式安裝的 JDK。它們使用自己的 cacerts。
  • 內含 JDK 的容器映像檔。在 BellSoft 測試的映像檔中,更新作業系統的 ca-certificates 套件並不會改變 JDK 的 cacerts。拉取較新的基礎映像檔也沒有幫助:經測試,10 月 21 日的 Liberica 映像檔仍信任全部十二台測試主機。
  • 使用自有信任庫的應用程式,也就是透過 javax.net.ssl.trustStore、在應用程式伺服器設定中,或在應用程式內部所設定的信任庫。請檢查其中是否包含這兩個根憑證。
  • 發行版封裝的 OpenJDK。檢查 cacerts 指向何處。如果它連結到系統 CA 存放區(在 Arch Linux 上,它是指向 /etc/ca-certificates/extracted/java-cacerts.jks 的符號連結),那麼發行版的 ca-certificates 更新就能修正這個問題。揭露頁面列出了已修補的套件版本。

如何檢查您的 JDK

首先,在信任庫中尋找這兩個根憑證。各廠商使用的別名不同(Liberica JDK 使用 emsigneccrootcag3 [jdk],Temurin 使用 cn_emsign_ecc_root_ca__g3,...),因此請依憑證主體(subject)搜尋。另外還有其他幾個 emSign 根憑證,但只有 emSign ECC Root CA - G3 受到影響。在 JDK 9 及更新版本上:

keytool -list -v -cacerts -storepass changeit | grep -E '^Alias name:|^Owner:' | grep -B1 -E '^Owner: CN=(emSign ECC Root CA - G3|AAA Certificate Services),'

Java 8 的 keytool 沒有 -cacerts 選項,而且其信任庫位於 jre/ 底下:

keytool -list -v -keystore "$JAVA_HOME/jre/lib/security/cacerts" -storepass changeit | grep -E '^Alias name:|^Owner:' | grep -B1 -E '^Owner: CN=(emSign ECC Root CA - G3|AAA Certificate Services),'

每個符合項目都會先列出別名,再列出憑證主體。請記下這些別名,修復時會用到。

接著測試實際連線。下面這個小程式會連線到全部十二台測試主機,可在 Java 8 及之後的所有版本上執行:

import java.net.URI;
import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLHandshakeException;
public class Check {
    public static void main(String[] args) {
        for (String level : new String[]{"dv", "ov", "ev"})
            for (String gen : new String[]{"g3b", "g3c"})
                for (String via : new String[]{"", "-aaa"}) {
                    String host = level + "-" + gen + via + "-emudhra.we-have-a-problem.com";
                    try {
                        HttpsURLConnection c = (HttpsURLConnection) URI.create("https://" + host).toURL().openConnection();
                        c.setConnectTimeout(10000);
                        c.setReadTimeout(10000);
                        c.connect();
                        c.disconnect();
                        System.out.println("TRUSTED       " + host);
                    } catch (SSLHandshakeException e) {
                        System.out.println("TLS_REJECTED  " + host);
                    } catch (Exception e) {
                        System.out.println("ERROR         " + host + " (" + e + ")");
                    }
                }
    }
}

在 JDK 11 或更新版本上,使用 java Check.java 執行。在 Java 8 上,或在只安裝了 JRE 的環境中,請先用 javac Check.java 編譯(必要時可在裝有 JDK 的機器上編譯),再執行 java Check。出現 TRUSTED 這一行,表示此 JDK 接受了已外洩的中繼憑證。ERROR 表示無法連到該主機,這既無法證明有問題,也無法證明沒問題。請使用與您的應用程式相同的 JDK 和信任庫設定執行檢查,包括在您的容器內部。

如何修復

1. 移除這兩個根憑證(在修正版 JDK 推出前建議採用)

這兩個根憑證,正是揭露頁面建議 Linux 和 macOS 使用者取消信任的那兩個。請以擁有該 JDK 的使用者身分,搭配檢查時找到的別名執行指令。以 Liberica JDK 為例:

keytool -delete -cacerts -storepass changeit -alias "comodoaaaca [jdk]"
keytool -delete -cacerts -storepass changeit -alias "emsigneccrootcag3 [jdk]"

在 Java 8 上,請將 -cacerts 替換為 -keystore "$JAVA_HOME/jre/lib/security/cacerts"。如果您的信任庫只包含這兩個根憑證中的一個(例如 Temurin 的信任庫),則只移除那一個。

如果您無法修改 JDK 安裝,可以編輯一份副本,並讓 JVM 改用它:

cp "$JAVA_HOME/lib/security/cacerts" ./trimmed-cacerts
keytool -delete -keystore ./trimmed-cacerts -storepass changeit -alias "comodoaaaca [jdk]"
keytool -delete -keystore ./trimmed-cacerts -storepass changeit -alias "emsigneccrootcag3 [jdk]"
java -Djavax.net.ssl.trustStore=./trimmed-cacerts -Djavax.net.ssl.trustStorePassword=changeit -jar app.jar

在我們的測試中,這兩種方法都讓 Liberica JDK 8 和 21 拒絕了全部十二台測試主機,而 sectigo.com 和 github.com 等一般網站仍可正常連線。

對於容器映像檔,請在建置時移除這些根憑證,讓每個新映像檔都已完成修復:

FROM bellsoft/liberica-runtime-container:jdk-21-musl
RUN keytool -delete -cacerts -storepass changeit -alias "comodoaaaca [jdk]" \
 && keytool -delete -cacerts -storepass changeit -alias "emsigneccrootcag3 [jdk]"

Distroless 映像檔和非 root 映像檔需要做兩項調整:它們沒有 shell,因此請使用 RUN 的 exec 形式,每行一個指令;它們以非特權使用者身分執行,因此編輯時請切換為 USER root,完成後再切回映像檔原本的使用者。

提示:移除根憑證的影響範圍比封鎖八個中繼憑證更大。若某台合法伺服器的憑證鏈只終止於這兩個根憑證之一,它將無法再正常運作。在全面部署此變更前,請先測試您的應用程式所呼叫的服務;並在每次 JDK 更新後再次執行檢查,因為更新或重新安裝可能會還原原始的 cacerts。

2. 啟用撤銷檢查(選用)

eMudhra 已撤銷全部八個中繼憑證,因此啟用撤銷檢查的 JVM 不必修改信任庫就會拒絕它們。請啟用 CRL 發布點和 OCSP:

echo 'ocsp.enable=true' > revocation.security
java -Dcom.sun.net.ssl.checkRevocation=true \
     -Dcom.sun.security.enableCRLDP=true \
     -Djava.security.properties=revocation.security \
     -jar app.jar

在我們的測試中,這項設定讓 Liberica JDK 8 和 21 以及 Temurin 21 拒絕了全部十二台測試主機。不過,它會改變所有憑證的驗證方式。JVM 會在路徑驗證期間進行 OCSP 或 CRL 查詢,當無法確定撤銷狀態時,交握可能會失敗。在全域啟用之前,請測試您的伺服器能否連到這些回應伺服器、它們會增加多少延遲,以及查詢失敗時的行為。如果您原本就已啟用撤銷檢查,或要將其作為移除根憑證的輔助措施,可以採用此方法。

3. 修正版建置推出後更新 JDK

BellSoft 表示正在準備移除這些根憑證的 Liberica JDK 更新。請留意您所用 JDK 廠商的安全性公告。更新推出後,請閱讀其版本資訊,確認已涵蓋 eMudhra 憑證,然後安裝更新、重新建置容器映像檔,並再次執行檢查。光是版本號碼較新,並不足以證明問題已修正。

修復之後:評估您的暴露程度

這些私鑰在大約十八個月內都能被還原,因此修復只是把門關上,卻無法告訴您是否有人曾經利用過它。對於會傳送認證資訊或敏感資料的系統,請在 TLS 檢測、代理伺服器和端點日誌中,搜尋由這八個 eMudhra 中繼憑證簽發的憑證。若發現任何可疑情況,請輪替受影響的認證資訊。

長遠來看,請像對待相依套件一樣對待信任庫:清楚每個信任庫位於何處、由誰負責更新,以及變更多快能套用到正式環境。這次事件顯示,當您的伺服器執行著自己的一份信任清單副本時,光靠瀏覽器更新是不夠的。

需要協助嗎?LOGIQ 提供 OpenJDK 和 Linux 的支援服務,包括安全性修補。如果您需要協助找出環境中的每一個 JDK 和信任庫,或安全地部署修正,歡迎與我們聯絡。

延伸閱讀