Java RMI 笔记
- 4 mins0x01 简介
RMI (Java Remote Method Invocation) Java 远程方法调用,是一种允许一个 JVM 上的 object 调用另一个 JVM 上 object 方法的机制
RMI 可以使用以下协议实现:
- Java Remote Method Protocol (JRMP):专门为 RMI 设计的协议
- Internet Inter-ORB Protocol (IIOP) :基于
CORBA
实现的跨语言协议
RMI 程序通常包括
-
rmi registry
naming service,提供 remote object 注册,name 到 remote object 的绑定和查询,是一种特殊的 remote object -
rmi server
创建 remote object,将其注册到 RMI registry -
rmi client
通过 name 向 RMI registry 获取 remote object reference (stub),调用其方法
官方文档中的图例
通常 RMI server 和 registry 运行在同一个 host 的不同端口上
RMI Registry 默认运行在 1099 端口上
RMI URL
rmi://hostname:port/remoteObjectName
具体参考 RMI Overview
0x02 示例
参考 Getting Started Using Java RMI
定义 remote 接口和方法
package com.b1ngz.sec.rmi;
import java.rmi.Remote;
import java.rmi.RemoteException;
public interface Hello extends Remote {
String sayHello() throws RemoteException;
}
需要实现 Remote
接口,接口方法需要抛出 RemoteException
或其父类的异常
实现 server
package com.b1ngz.sec.rmi;
import java.rmi.RemoteException;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
import java.rmi.server.UnicastRemoteObject;
public class Server implements Hello {
public static void main(String[] args) {
try {
Server obj = new Server();
Hello stub = (Hello) UnicastRemoteObject.exportObject(obj, 56666);
Registry registry = LocateRegistry.createRegistry(11099);
registry.bind("Hello", stub);
} catch (Exception e) {
System.out.println("Server Exception: " + e.toString());
e.printStackTrace();
}
}
public String sayHello() throws RemoteException {
return "Hello, World";
}
}
Server
类实现了 Hello
接口,在 main 函数中创建并导出 remote object,接着将 remote object 注册到 RMI registry 中
UnicastRemoteObject.exportObject(obj, 56666)
方法执行完后,会运行 rmi server,监听在本地 56666 端口,等待 client 的请求。exportObject()
方法返回结果为 remote object stub (代理对象,实现了与 Hello
接口同样的方法,包含 rmi server 的 host、port 信息)
LocateRegistry.createRegistry(11099);
执行完后,会创建并启动 RMI registry,监听在本地 11099 端口
registry.bind("Hello", stub);
将 stub
注册到 registry,并与 name Hello
绑定
实现 client
package com.b1ngz.sec.rmi;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
public class Client {
public static void main(String[] args) {
String host = "localhost";
int port = 11099;
try {
Registry registry = LocateRegistry.getRegistry(host, port);
Hello stub = (Hello) registry.lookup("Hello");
String response = stub.sayHello();
System.out.println("response: " + response);
} catch (Exception e) {
System.err.println("Client exception: " + e.toString());
e.printStackTrace();
}
}
}
LocateRegistry.getRegistry(host, port);
获取 rmi registry
registry.lookup("Hello")
获取 remote object stub
调用 stub 的 sayHello()
方法背后的流程:
- client 端通过 stub 中包含的 host、port 信息,与 remote object 所在的 server 建立连接 ,然后序列化调用数据
- server 端接收调用请求,将调用转发给 remote object,然后序列化结果,返回给 client
- client 端接收、反序列化结果
0x03 安全
在远程方法调用过程中,参数需要先序列化,从 local JVM 发送到 remote JVM,然后在 remote JVM 上反序列化,执行完后,将结果序列化,发送回 local JVM,因此可能会存在反序列化漏洞
此外,RMI 有一个特性,即当 class 在 receiver 的 JVM 中没有定义时,可以动态从本地 / 远程加载 object class ,在默认情况下 ( JDK 7u21
起),只允许从本地加载,即 java.rmi.server.useCodebaseOnly
为 true
,并且有 Security Manager 的存在,因此利用比较困难