RMI反序列化及相关工具反制浅析
字数 1562 2025-08-09 13:33:35

RMI反序列化漏洞分析与工具反制研究

1. RMI基础与调试方法

1.1 RMI基本组件

  • Registry: 注册中心,管理远程对象引用
  • Server: 提供远程服务的实现
  • Client: 调用远程服务的客户端

1.2 RMI调试技巧

调试RMI服务的关键断点位置:

sun/rmi/server/UnicastServerRef#dispatch

调试建议:

  • Client和Server分开两个项目运行
  • 使用LocateRegistry.createRegistry()创建Registry
  • 通过Naming.lookup()建立连接

2. RMI反序列化漏洞分析

2.1 攻击Registry端(< JDK8u121)

漏洞原理

  • 低版本JDK允许远程主机向Registry进行bind()操作
  • Registry在调用RegistryImpl#bind()前会先反序列化客户端数据
  • 反序列化后才进行主机验证(checkAccess())

攻击方法

  1. 使用对象代理+AnnotationInvocationHandler

    • 对象代理实现Remote接口绕过类型检查
    • 使用AnnotationInvocationHandler作为InvocationHandler触发CC1链
  2. 仿写Naming.bind()发送任意对象

    // 示例POC核心代码
    LiveRef liveRef = new LiveRef(new ObjID(ObjID.REGISTRY_ID), 
        new TCPEndpoint("127.0.0.1", 1099, null, null), false);
    UnicastRef unicastRef = new UnicastRef(liveRef);
    RemoteCall remoteCall = unicastRef.newCall(registryImpl_stub, operations, 0, 4905912898345647071L);
    ObjectOutput outputStream = remoteCall.getOutputStream();
    outputStream.writeObject(hashMap); // 发送恶意对象
    unicastRef.invoke(remoteCall);
    

2.2 攻击Registry端(JDK8u121 <= & < jdk8u242-b07)

防御机制

  1. IP检查前置RegistryImpl#bind()先检查来源IP再反序列化
  2. JEP290白名单RegistryImpl#registryFilter()限制反序列化类

绕过方法

  1. 使用lookup请求:相比bind请求,lookup不检查来源IP
  2. 利用白名单中的类
    • RemoteObjectInvocationHandler在白名单中
    • 通过RemoteObjectInvocationHandler触发二次连接

攻击步骤

  1. 自定义ysoserial,修改JRMPClientpayload:

    public RemoteObjectInvocationHandler getRemoteObjectInvocationHandler(final String command) {
        // 构造包含恶意服务器地址的RemoteObjectInvocationHandler
    }
    
  2. 发送恶意lookup请求:

    Operation[] operations = new Operation[]{...};
    LiveRef liveRef = new LiveRef(new ObjID(ObjID.REGISTRY_ID), 
        new TCPEndpoint(host, port, null, null), false);
    UnicastRef unicastRef = new UnicastRef(liveRef);
    RemoteCall remoteCall = unicastRef.newCall(remoteStubTmp, operations, 2, 4905912898345647071L);
    ObjectOutput var3 = remoteCall.getOutputStream();
    var3.writeObject(r); // 写入恶意RemoteObjectInvocationHandler
    unicastRef.invoke(remoteCall);
    
  3. 服务端反连恶意JRMP Server触发反序列化

2.3 直接攻击Registry的限制(>= jdk8u242-b07)

  • 新版本使用readString()替代readObject()避免直接反序列化
  • 目前无法直接通过Client端发送payload攻击Registry

2.4 攻击Client端

漏洞原理

  • Client在RegistryImpl_Stub#lookup中反序列化Registry返回的数据
  • 恶意Registry可构造特殊返回值触发反序列化

攻击点

// sun.rmi.transport.StreamRemoteCall#executeCall
var1 = this.in.readByte();
switch (var1) {
    case 2: 
        var14 = this.in.readObject(); // 反序列化点

3. RMI工具反制分析

3.1 ysoserial - exploit/RMIRegistryExploit

  • 使用registry.list()操作
  • 调用链:UnicastRef#invoke → 触发Client端反序列化

3.2 RmiTaste

  • connect模式:使用RegistryImpl_Stub#list探测
  • 所有模式最终都调用Enumerate#connect
  • 存在被反制风险

3.3 rmiscout

  • 所有模式使用RMIConnector发起连接
  • 核心调用RegistryImpl_Stub#list
  • 存在被反制风险

4. 防御与反制措施

4.1 设置安全策略

创建policy.txt

grant {
    permission java.security.AllPermission "*";
};

4.2 启用安全管理器与序列化过滤

运行命令:

java -Djava.security.manager \
     -Djava.security.policy=policy.txt \
     -Djdk.serialFilter=!java.net.URL \
     -jar tool.jar

4.3 序列化Filter配置

  • 黑名单:!java.net.URL(阻止URLDNS)
  • 可根据需要添加其他反序列化链关键类

5. 关键调用链总结

5.1 Registry端攻击链

RegistryImpl_Skel#dispatch
  → readObject() // 反序列化点
  → RegistryImpl#bind

5.2 Client端攻击链

RegistryImpl_Stub#lookup
  → UnicastRef#invoke
  → StreamRemoteCall#executeCall
    → readObject() // 反序列化点

5.3 工具反制链

工具调用registry.list()
  → RegistryImpl_Stub#list
  → UnicastRef#invoke
  → StreamRemoteCall#executeCall
    → readObject() // 反序列化点
RMI反序列化漏洞分析与工具反制研究 1. RMI基础与调试方法 1.1 RMI基本组件 Registry : 注册中心,管理远程对象引用 Server : 提供远程服务的实现 Client : 调用远程服务的客户端 1.2 RMI调试技巧 调试RMI服务的关键断点位置: 调试建议: Client和Server分开两个项目运行 使用 LocateRegistry.createRegistry() 创建Registry 通过 Naming.lookup() 建立连接 2. RMI反序列化漏洞分析 2.1 攻击Registry端( < JDK8u121) 漏洞原理 低版本JDK允许远程主机向Registry进行 bind() 操作 Registry在调用 RegistryImpl#bind() 前会先反序列化客户端数据 反序列化后才进行主机验证( checkAccess() ) 攻击方法 使用对象代理+AnnotationInvocationHandler 对象代理实现 Remote 接口绕过类型检查 使用 AnnotationInvocationHandler 作为InvocationHandler触发CC1链 仿写Naming.bind()发送任意对象 2.2 攻击Registry端(JDK8u121 <= & < jdk8u242-b07) 防御机制 IP检查前置 : RegistryImpl#bind() 先检查来源IP再反序列化 JEP290白名单 : RegistryImpl#registryFilter() 限制反序列化类 绕过方法 使用lookup请求 :相比bind请求,lookup不检查来源IP 利用白名单中的类 : RemoteObjectInvocationHandler 在白名单中 通过 RemoteObjectInvocationHandler 触发二次连接 攻击步骤 自定义ysoserial,修改 JRMPClient payload: 发送恶意lookup请求: 服务端反连恶意JRMP Server触发反序列化 2.3 直接攻击Registry的限制(>= jdk8u242-b07) 新版本使用 readString() 替代 readObject() 避免直接反序列化 目前无法直接通过Client端发送payload攻击Registry 2.4 攻击Client端 漏洞原理 Client在 RegistryImpl_Stub#lookup 中反序列化Registry返回的数据 恶意Registry可构造特殊返回值触发反序列化 攻击点 3. RMI工具反制分析 3.1 ysoserial - exploit/RMIRegistryExploit 使用 registry.list() 操作 调用链: UnicastRef#invoke → 触发Client端反序列化 3.2 RmiTaste connect模式 :使用 RegistryImpl_Stub#list 探测 所有模式最终都调用 Enumerate#connect 存在被反制风险 3.3 rmiscout 所有模式使用 RMIConnector 发起连接 核心调用 RegistryImpl_Stub#list 存在被反制风险 4. 防御与反制措施 4.1 设置安全策略 创建 policy.txt : 4.2 启用安全管理器与序列化过滤 运行命令: 4.3 序列化Filter配置 黑名单: !java.net.URL (阻止URLDNS) 可根据需要添加其他反序列化链关键类 5. 关键调用链总结 5.1 Registry端攻击链 5.2 Client端攻击链 5.3 工具反制链