记一次对RMI的理解(避坑版)
字数 1622 2025-08-29 22:41:38

RMI 远程方法调用深入解析与安全实践

1. RMI 基础概念

1.1 RMI 定义

RMI (Remote Method Invocation) 是 Java 独有的远程方法调用机制,允许一个 JVM 上的对象调用另一个 JVM 中对象的方法。

1.2 核心组件

  • Stub (存根):客户端代理类,包含服务器 Skeleton 信息
  • Skeleton (骨架):服务端代理类
  • RMI Registry:注册表服务,默认监听 1099 端口
  • 远程对象:必须实现 java.rmi.Remote 接口,通常继承 UnicastRemoteObject

1.3 关键特性

  • 客户端和服务器不直接通信,采用代理方式
  • 只有远程接口中声明的方法才能被远程调用
  • 传输数据需要序列化/反序列化
  • 参数和返回值必须实现 java.io.Serializable 接口
  • 客户端和服务端的 serialVersionUID 必须一致

2. RMI 工作流程

  1. 服务端创建远程对象并注册到 Registry
  2. 客户端访问 Registry 查找远程对象
  3. Registry 返回 RMI 代理所需的 Stub
  4. 客户端通过 Stub 调用远程方法
  5. Stub 与 Skeleton 通信(数据序列化传输)
  6. Skeleton 反序列化数据并调用实际方法
  7. 执行结果序列化后返回给 Stub
  8. Stub 将结果返回给客户端

3. 环境搭建实践

3.1 服务端配置 (Linux)

  1. 安装 Java 环境
  2. 创建 RMIServer.java 文件示例:
package RMIServer;

import java.rmi.Naming;
import java.rmi.Remote;
import java.rmi.RemoteException;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
import java.rmi.server.UnicastRemoteObject;

public class RMIServer {
    public interface IRemoteHelloWorld extends Remote {
        public String hello() throws RemoteException;
    }
    
    public class RemoteHelloWorld extends UnicastRemoteObject 
        implements IRemoteHelloWorld {
        protected RemoteHelloWorld() throws RemoteException {
            super();
        }
        public String hello() throws RemoteException {
            System.out.println("call from");
            return "Hello world";
        }
    }
    
    private void start() throws Exception {
        RemoteHelloWorld h = new RemoteHelloWorld();
        LocateRegistry.createRegistry(1099);
        Naming.rebind("rmi://127.0.0.1:1099/Hello", h);
    }
    
    public static void main(String[] args) throws Exception {
        new RMIServer().start();
    }
}
  1. 编译并运行:
javac RMIServer.java
java -cp . RMIServer/RMIServer

3.2 客户端配置 (Windows)

  1. 安装 Java 环境和 Wireshark
  2. 创建 RMIClient 代码:
package org;
import org.vulhub.RMI.RMIServer;
import java.rmi.Naming;
import java.rmi.NotBoundException;
import java.rmi.RemoteException;

public class TrainMain {
    public static void main(String[] args) throws Exception {
        RMIServer.IRemoteHelloWorld hello = (RMIServer.IRemoteHelloWorld)
            Naming.lookup("rmi://192.168.220.129:1099/Hello");
        String ret = hello.hello();
        System.out.println(ret);
    }
}

4. 常见问题与解决方案

4.1 JEP290 限制

  • 影响版本:从 JDK 8u121、7u13、6u141 开始引入
  • 作用:为 RMI 注册表和分布式垃圾收集器内置过滤器,限制反序列化类
  • 表现:Registry 拒绝特定类的反序列化
  • 解决方案
    • 使用低于限制版本的 JDK
    • 配置允许的类进行反序列化

4.2 包名不一致问题

  • 症状:客户端无法正确调用远程方法
  • 原因:客户端和服务端远程对象对应的包名不一致
  • 解决方案
    • 确保两端代码完全一致,包括包路径
    • 示例修复:
      // 服务端和客户端必须使用相同的包名
      package RMIServer;
      

4.3 主机名解析问题

  • 症状:连接被重定向到 127.0.1.1
  • 原因:/etc/hosts 文件配置导致 IP 映射错误
  • 解决方案
    1. 修改 /etc/hosts 文件,确保正确映射
    2. 或者在启动 RMIServer 时指定主机名:
      java -Djava.rmi.server.hostname=192.168.220.129 -cp . RMIServer/RMIServer
      

5. 安全分析与漏洞利用

5.1 RMI 反序列化漏洞

  • 漏洞位置:Stub 与 Skeleton 通信时的序列化数据传输
  • 利用条件
    • JDK 版本低于 JEP290 限制版本
    • 存在可利用的反序列化链
  • 防护措施
    • 升级 JDK 版本
    • 实施严格的序列化过滤器

5.2 网络流量分析

使用 Wireshark 抓包可观察到:

  1. TCP 三次握手建立连接
  2. 客户端查询注册对象
  3. 服务端返回 Stub 信息(包含 IP 和端口)
  4. 客户端与远程对象直接通信
  5. 序列化数据交换过程

6. 最佳实践总结

  1. 环境一致性:确保客户端和服务端使用相同的包结构和类定义
  2. 版本控制:注意 JDK 版本对安全特性的影响
  3. 网络配置:正确配置主机名解析和网络连接
  4. 安全防护
    • 使用最新支持的 JDK 版本
    • 限制可序列化的类
    • 监控 RMI 通信流量
  5. 调试技巧
    • 使用 Wireshark 分析网络交互
    • 关注序列化/反序列化过程中的异常

附录:关键术语解释

  • JVM (Java 虚拟机):Java 程序运行容器,提供跨平台执行、自动内存管理和安全控制
  • 序列化:将对象转换为字节流的过程
  • 反序列化:将字节流重建为对象的过程
  • Registry:RMI 注册表服务,维护远程对象绑定关系
RMI 远程方法调用深入解析与安全实践 1. RMI 基础概念 1.1 RMI 定义 RMI (Remote Method Invocation) 是 Java 独有的远程方法调用机制,允许一个 JVM 上的对象调用另一个 JVM 中对象的方法。 1.2 核心组件 Stub (存根) :客户端代理类,包含服务器 Skeleton 信息 Skeleton (骨架) :服务端代理类 RMI Registry :注册表服务,默认监听 1099 端口 远程对象 :必须实现 java.rmi.Remote 接口,通常继承 UnicastRemoteObject 类 1.3 关键特性 客户端和服务器不直接通信,采用代理方式 只有远程接口中声明的方法才能被远程调用 传输数据需要序列化/反序列化 参数和返回值必须实现 java.io.Serializable 接口 客户端和服务端的 serialVersionUID 必须一致 2. RMI 工作流程 服务端创建远程对象并注册到 Registry 客户端访问 Registry 查找远程对象 Registry 返回 RMI 代理所需的 Stub 客户端通过 Stub 调用远程方法 Stub 与 Skeleton 通信(数据序列化传输) Skeleton 反序列化数据并调用实际方法 执行结果序列化后返回给 Stub Stub 将结果返回给客户端 3. 环境搭建实践 3.1 服务端配置 (Linux) 安装 Java 环境 创建 RMIServer.java 文件示例: 编译并运行: 3.2 客户端配置 (Windows) 安装 Java 环境和 Wireshark 创建 RMIClient 代码: 4. 常见问题与解决方案 4.1 JEP290 限制 影响版本 :从 JDK 8u121、7u13、6u141 开始引入 作用 :为 RMI 注册表和分布式垃圾收集器内置过滤器,限制反序列化类 表现 :Registry 拒绝特定类的反序列化 解决方案 : 使用低于限制版本的 JDK 配置允许的类进行反序列化 4.2 包名不一致问题 症状 :客户端无法正确调用远程方法 原因 :客户端和服务端远程对象对应的包名不一致 解决方案 : 确保两端代码完全一致,包括包路径 示例修复: 4.3 主机名解析问题 症状 :连接被重定向到 127.0.1.1 原因 :/etc/hosts 文件配置导致 IP 映射错误 解决方案 : 修改 /etc/hosts 文件,确保正确映射 或者在启动 RMIServer 时指定主机名: 5. 安全分析与漏洞利用 5.1 RMI 反序列化漏洞 漏洞位置 :Stub 与 Skeleton 通信时的序列化数据传输 利用条件 : JDK 版本低于 JEP290 限制版本 存在可利用的反序列化链 防护措施 : 升级 JDK 版本 实施严格的序列化过滤器 5.2 网络流量分析 使用 Wireshark 抓包可观察到: TCP 三次握手建立连接 客户端查询注册对象 服务端返回 Stub 信息(包含 IP 和端口) 客户端与远程对象直接通信 序列化数据交换过程 6. 最佳实践总结 环境一致性 :确保客户端和服务端使用相同的包结构和类定义 版本控制 :注意 JDK 版本对安全特性的影响 网络配置 :正确配置主机名解析和网络连接 安全防护 : 使用最新支持的 JDK 版本 限制可序列化的类 监控 RMI 通信流量 调试技巧 : 使用 Wireshark 分析网络交互 关注序列化/反序列化过程中的异常 附录:关键术语解释 JVM (Java 虚拟机) :Java 程序运行容器,提供跨平台执行、自动内存管理和安全控制 序列化 :将对象转换为字节流的过程 反序列化 :将字节流重建为对象的过程 Registry :RMI 注册表服务,维护远程对象绑定关系