説明
JNDIは名前を使ってオブジェクトやコンテキストを検索します。lookup、lookupLink、InitialContext.doLookupの名前に信頼できない値を渡すと、攻撃者がアプリケーションに解決させるリソース識別子を選べます。
InitialContextはscheme_id:...形式の文字列をURL名として扱い、そのスキームのURLコンテキストファクトリーを探します。LDAP、RMI、DNSや独自のプロバイダーを通じて、意図しない接続、リソースへのアクセス、情報漏えい、遅延が起こり得ます。返された参照からオブジェクトを再構成する際には、JDK、プロバイダー、ファクトリー、クラスパス、直列化設定によってコード実行に至る場合もあります。
新しいJDKの保護にも適用範囲があります。JDK 24は組み込みLDAP/RMIプロバイダーによるリモートのファクトリークラスのダウンロードを恒久的に無効化し、従来のtrustURLCodebaseプロパティを削除しました。JDK 25の組み込みLDAPプロバイダーは、com.sun.jndi.ldap.object.trustSerialDataを明示的にtrueにしない限り、直列化データや関連する参照を再構成しません。ただし、URL名による接続や、ローカルファクトリー、独自のプロバイダー、ObjectFactoryBuilderの危険性は残ります。
想定される影響
- 攻撃者が選んだLDAP、RMI、DNS、独自プロバイダーへのサーバー側接続
- 内部の名前サービスへのアクセスや、ファイアウォール内の情報の漏えい
- 遅い、または応答しないプロバイダーによるスレッド・接続資源の枯渇
- 危険な旧設定、直列化、ファクトリー構成でのオブジェクト生成の副作用やコード実行
対処方法
- 完全なJNDI名を開発者が管理してください。外部からは業務上のキーを受け取り、閉じた
switch、enumなどで固定のローカル名に対応付けます。信頼できない接尾辞を連結しないでください。 - 名前を直接受け取る必要がある場合は、確認済みの完全なローカル名の有限集合と完全一致で比較してください。接頭辞、正規表現、
ldap:やrmi:などのスキームの拒否リストだけでは、すべてのプロバイダーや連合先を確認できません。 Context.INITIAL_CONTEXT_FACTORY、Context.PROVIDER_URL、Context.OBJECT_FACTORIES、Context.URL_PKG_PREFIXESなどを信頼できる配備設定で固定してください。クラスパスには信頼できるプロバイダーとファクトリーだけを置き、独自のObjectFactoryBuilderは別途確認します。- サポート対象の更新済みJDK 25を使用してください。削除された
trustURLCodebaseの設定は対策になりません。LDAPのtrustSerialDataは有効にせず、jdk.jndi.object.factoriesFilterとプロバイダー固有のフィルターを必要なファクトリーに限定してください。 - 送信先を必要な名前サーバーに絞り、最小権限の認証情報を使ってください。組み込みLDAPでは
com.sun.jndi.ldap.connect.timeoutとcom.sun.jndi.ldap.read.timeoutを設定します。これらは影響を抑えますが、攻撃者が指定する名前を安全にはしません。
例
変更前
java
import javax.naming.Context;
import javax.naming.InitialContext;
import javax.naming.NamingException;
import javax.servlet.http.HttpServletRequest;
class UnsafeJndiLookup {
Object lookup(HttpServletRequest request) throws NamingException {
String name = request.getParameter("name");
Context context = new InitialContext();
return context.lookup(name); // BAD: リクエスト値がプロバイダーとリソースを選択できる
}
}
変更後
外部のキーを、アプリケーションが管理する完全なローカル名に変換します。switchの結果にはリクエストの文字列を含めません。
java
import javax.naming.InitialContext;
import javax.naming.NamingException;
import javax.servlet.http.HttpServletRequest;
class SafeJndiLookup {
Object lookup(HttpServletRequest request) throws NamingException {
String key = request.getParameter("name");
if (key == null) {
throw new NamingException("resource not selected");
}
String jndiName = switch (key) {
case "mainDs" -> "java:comp/env/jdbc/MainDS";
case "ordersQ" -> "java:comp/env/jms/OrdersQueue";
default -> throw new NamingException("resource not allowed");
};
return new InitialContext().lookup(jndiName);
}
}
参考資料
- Java SE 25
ContextAPI - Java SE 25 InitialContext API
- Java SE 25
java.namingModule - JDK-8338536: Permanently Disable Remote Code Downloading in JNDI
- JDK 25.0.4.1 Release Notes
- Oracle JNDI: URL Context Factory
- Oracle JNDI: Security
- CWE-99: Improper Control of Resource Identifiers
- OWASP Top 10:2025 A05 Injection
- OWASP Top 10:2021 A03 Injection
- OWASP ASVS 5.0