记一次对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 工作流程
- 服务端创建远程对象并注册到 Registry
- 客户端访问 Registry 查找远程对象
- Registry 返回 RMI 代理所需的 Stub
- 客户端通过 Stub 调用远程方法
- Stub 与 Skeleton 通信(数据序列化传输)
- Skeleton 反序列化数据并调用实际方法
- 执行结果序列化后返回给 Stub
- Stub 将结果返回给客户端
3. 环境搭建实践
3.1 服务端配置 (Linux)
- 安装 Java 环境
- 创建 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();
}
}
- 编译并运行:
javac RMIServer.java
java -cp . RMIServer/RMIServer
3.2 客户端配置 (Windows)
- 安装 Java 环境和 Wireshark
- 创建 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 映射错误
- 解决方案:
- 修改 /etc/hosts 文件,确保正确映射
- 或者在启动 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 抓包可观察到:
- TCP 三次握手建立连接
- 客户端查询注册对象
- 服务端返回 Stub 信息(包含 IP 和端口)
- 客户端与远程对象直接通信
- 序列化数据交换过程
6. 最佳实践总结
- 环境一致性:确保客户端和服务端使用相同的包结构和类定义
- 版本控制:注意 JDK 版本对安全特性的影响
- 网络配置:正确配置主机名解析和网络连接
- 安全防护:
- 使用最新支持的 JDK 版本
- 限制可序列化的类
- 监控 RMI 通信流量
- 调试技巧:
- 使用 Wireshark 分析网络交互
- 关注序列化/反序列化过程中的异常
附录:关键术语解释
- JVM (Java 虚拟机):Java 程序运行容器,提供跨平台执行、自动内存管理和安全控制
- 序列化:将对象转换为字节流的过程
- 反序列化:将字节流重建为对象的过程
- Registry:RMI 注册表服务,维护远程对象绑定关系