セキュリティ研究者のマイケル・ステパンキン氏は、適切に実装されていない「mTLS認証」は認証情報が盗み出される可能性があると警告する記事をGitHubブログで公開した。
この記事は会員限定です。会員登録(無料)すると全てご覧いただけます。
GitHubでセキュリティ研究者のマイケル・ステパンキン氏は2023年8月17日(米国時間)、相互TLS認証(mTLS)の誤った実装によるセキュリティリスクを警告する記事をGitHubの公式ブログで公開した。
TLS認証は、GmailやGitHubなどのWebサイトに接続する際、クライアントであるブラウザ側は、サーバから提供された証明書を確認し、本当に接続したいWebサイトであるかどうかを検証している。mTLS認証は、クライアントとサーバの両方が本当に正しいクライアント、サーバかどうかを相互で検証、認証する方式を指す。
ステパンキン氏は「近年、ゼロトラストネットワークにおいて、クライアント認証の手段として、mTLS認証が使用されるようになっている。X.509証明書に基づくmTLS認証は、パスワードやトークンに比べて利点があるが、複雑さも増す」と指摘する。
JavaでmTLS認証を利用する場合、最低限必要な設定は、アプリケーションの設定でmTLSを有効にし、信頼できるルート証明書の場所を以下のように指定することだ。
$ cat application.properties … server.ssl.client-auth=need server.ssl.trust-store=/etc/spring/server-truststore.p12 server.ssl.trust-store-password=changeit
またcurlなどのクライアントから、どの証明書をサーバに送信するか指定する必要がある。
$ curl -k -v –cert client.pem http://localhost/hello
実際のアプリケーションにおいては、開発者はしばしばTLSハンドシェイク中に提示された証明書にアクセスする必要がある。Javaにおいて、証明書にアクセスする一般的な方法は2つある。
X509Certificate[] certificates = sslSession.getPeerCertificates(); // another way X509Certificate[] certificates = request.getAttribute("javax.servlet.request.X509Certificate");
ステパンキン氏は、さまざまなアプリケーションにおいてこれらのAPIをどのように使用しているか調査した。ステパンキン氏によると、以下のような正しい実装例と危険な実装例を見つけたという。
String user = certificates[0].getSubjectX500Principal().getName();
//way 2 is dangerous for (X509Certificate cert : certificates) { if (isClientCertificate(cert)) { user = cert.getSubjectX500Principal().getName(); } }
危険な実装例では、特定の条件に合致する証明書を配列内で探索していた。ステパンキン氏は「JavaのTLSライブラリは、配列内の最初の証明書のみを検証し、厳密な順序での送信を要求していないためリスクがある」とした上で、シングルサインオンソフトウェアの「Keycloak」における不適切な証明書検証(CVE-2023-2422)を例に、問題点を解説した。
Keycloakでは、配列内の全ての証明書の中から、client_idフォーム、パラメーターに一致する証明書を検索する。一致する証明書を見つけた場合、TLSハンドシェイク中に検証されている証明書だとして信頼する。
X509Certificate[] certs = null; ClientModel client = null; try { certs = provider.getCertificateChain(context.getHttpRequest()); String client_id = null; ... if (formData != null) { client_id = formData.getFirst(OAuth2Constants.CLIENT_ID); } … matchedCertificate = Arrays.stream(certs) .map(certificate -> certificate.getSubjectDN().getName()) .filter(subjectdn -> subjectDNPattern.matcher(subjectdn).matches()) .findFirst();
ステパンキン氏は「クライアントは必要なだけ証明書を送信可能であり、サーバは最初の証明書のみを検証するため、攻撃者は、一連の動作を悪用し、別のユーザー名で認証する可能性がある」と指摘した。その上で対策方法として、配列の最初にある証明書のみを使用すべきとした。
大規模なシステムの場合、サーバが全てのルート証明書と中間証明書をローカルに保存せず、外部のストレージやデータベースを使用しているケースがある。この場合、証明書の拡張子を悪用したSSRF(Server Side Request Forgery)攻撃を起こされるリスクがあるという。また証明書の拡張仕様であるAIA(Authority Information Access)を利用している場合、証明書に含まれる「Subject」「Issuer」「Serial」などの情報を利用したインジェクション攻撃が起きるリスクもあるとする。
ステパンキン氏は対策方法として、外部のストレージやデータベースを使用する際には、証明書に記載されている証明書情報の各値は信頼できないものとして扱い、適切にバリデーションやエスケープ処理をすべきとした。
証明書が失効した(または無効化させる必要が生じた)場合、証明書が信頼できなくなり、セキュリティ上のリスクが生じる可能性があるため、適切な失効チェックの実施が求められる。失効情報の保存場所は、アプリケーション内にハードコーディングするか、拡張子を使って証明書自体から取得できる。
ステパンキン氏は、mTLS認証をサポートし、証明書の失効チェックを支援する「Apereo CAS」での認証情報の漏えい(CVE-2023-28857)を例に挙げ、「証明書が提供する失効情報のURLをアプリケーションが直接使用することは危険であり、攻撃者はそのURLを操作してSSRF攻撃やRCE(リモートコード実行)を試みる可能性がある」と警告している。
Copyright © ITmedia, Inc. All Rights Reserved.