RMI
简介
Remote Method Invocation,远程方法调用[目标与RPC相似]
是让某个JVM的对象调用另外一个JVM中的对象,Java独有
java调用Java程序(Java混合编程),分布式部署,多虚拟机JVM运行
多个进程可通过网络互相传递消息进行协作,clint和server
在学习Fastjson jdbcRowSetlmpl时候会用到,还有log4j2
RMI Server
- 一个继承了java.rmi.Remote接口,其中定义我们要远程调用的函数,如hello()
- 一个实现此接口并且继承于UnicastRemoteObject的实现类,远程对象的实现类必须继承于UnicastRemoteObject,只有继承了才能表示该类是一个远程对象
- 一个主类,用来创建Registry,并讲上面的类实例化后绑定到一个地址.
import sun.rmi.server.UnicastRef;
import java.io.IOException;
import java.rmi.Naming;
import java.rmi.Remote;
import java.rmi.RemoteException;
import java.rmi.registry.LocateRegistry;
import java.rmi.server.UnicastRemoteObject;
public class RMIserver {
public interface IRemoteHeloworld extends Remote{
public String hello() throws RemoteException;
}
public class RemoteHelloWorld extends UnicastRemoteObject implements IRemoteHeloworld{
public RemoteHelloWorld() throws RemoteException{
super();
}
@Override
public String hello() throws RemoteException {
System.out.println("call from");
try {
Runtime.getRuntime().exec("calc");
} catch (IOException e) {
e.printStackTrace();
}
return "hello world";
}
}
private void start() throws Exception{
RemoteHelloWorld remoteHelloWorld = new RemoteHelloWorld();
//创建并运行registry服务,且端口号为1099
LocateRegistry.createRegistry(1099);
//绑定对象,以Hello名字发布出去
Naming.rebind("rmi://127.0.0.1:1099/Hello",remoteHelloWorld);
}
public static void main(String[] args) {
try {
new RMIserver().start();
} catch (Exception e) {
e.printStackTrace();
}
}
}
RMI Client
- 使用Naming.lookup在Registry中寻找名字是Hello的对象,后门使用和在本地使用是一样的了
import java.rmi.Naming;
public class TrainMain {
public static void main(String[] args) throws Exception {
RMIserver.IRemoteHeloworld hello =(RMIserver.IRemoteHeloworld) Naming.lookup("rmi://172.20.10.7:1099/Hello");
String hello1 = hello.hello();
System.out.println(hello1);
}
}
虽然说执行远程方法的时候,代码是在远程服务器上执行的,但我们还需要知道有哪些方法可以调用
这时候便可以运行接口
理解
client不会直接调用server上的方法,会借助存根(stub)来进行代理访问server;同时骨架(Skeleton)是另一个代理
同时与真实对象一起在服务器上
骨架将接受到的请求交给服务器处理,服务器处理完成之后将结果打包发送至存根,然后存根将结果解包之后的结果发送给客户端
wirshark流量分析
ip.dst ==1.1.1.1
rmi || tcp.port eq 端口号
整个过程进行了两次TCP握手
第一次TCP连接是连接远端172.20.10.7的1099端口,二者沟通后,client向server发送一个”Call”消息,对应客户端连接Registry,并在其中寻找Name是Hello的对象,server回复一个”ReturnData”消息,对应Registry返回一个序列化的数据,这个就是找到Name=Hello的对象
然后新建一个TCP连接,连接server33769端口
客户端反序列化该对象,发现该对象是一个远程地址,地址在192.168.135.142:33769,于是再与这个地址建立TCP连接,在这个新的连接中,才执行真正的远程调用方法[hello()]
在这个ReturnData包中,返回目标192.168.135.142,后面紧跟一个字节\x00\x00\x83\xE9,刚好就是整数33769的网络序列
RMI Registry就像是一个网关,自己不会执行远程方法,但RMI server可以在上面注册一个Name到对象的绑定关系;RMI client通过Name向RMI registry查询,得到这个绑定关系,然后再连接RMI server;最后,远程方法实际上在RMI server上调用
序列化传输
RMI在数据传输过程中的对象须实现java.io.serializable接口,因为在传输过程中都是进行序列化进行传输并且客户端的serialVersionUID字段要与服务器端保持一致
ac ed就是反序列化的标志
RMI的主要构成
三部分
- RMIregistry注册表:服务实例将被注册表注册到特定的名称中
- RMI Server
- RMI Clint客户端:客户端通过查询注册表来获取对应的名称的对象引用,以及该对象实现的接口
RMI client会远程连接RMI registry(默认端口1099),然后会在registry寻找名字为Test的对象(假设此时客户端要调用test对象中的某个方法),registry会寻找对应名字的远程对象引用,并且序列化后进行返回(数据内容就是远程对象的地址,这里返回的对象就是存根stub),客户端在接受到之后首先会在本机中的classpath进行查找,如果没有找到则说明是远程对象,客户端就会与远程对象进行tcp连接
当我们尝试绑定远程服务器上的hello对象的时候,会报错
java对远程访问RMI registry做了限制,只有来源地址是localhost的时候,才能调用rebind.bind.unbind等方法
不过list和lookup方法可以远程调用
list可以列出目标上所有绑定的对象
String[] s =Naming.list("rmi://192.168.135.142:1099");
lookup作用就是获得某个远程对象
目标服务器上要是有危险方法,RMI便可调用
https://github.com/NickstaDB/BaRMIe
存根和骨架
当RMI server启动的时候的端口是被随机分配的,但是我们的rmi registry端口是1099
- 客户端通过远程连接registry获取存根,存根中包含了远程对象的定位信息。如socket端口.服务器主机地址等,并实现了远程调用过程中的具体的网络通信细节
- 由于存根是客户端的代理类,所以客户端可以调用存根上的方法
- 存根远程连接到服务器,提交对应的参数
- 骨架收到数据并对其进行反序列化,然后将发送给我们的server
- server执行之后将结果打包,传输给client
RMI的危害
利用codebase执行任意代码
曾经Java可以运行在浏览器中,利用Applet,在使用Applet的时候通常需要指定一个codebase属性
比如
<applet code="Helloworld.class" codebase="Applets" width="800" height="600">
</applet>
除了Applet,RMI中也存在远程加载的场景,也会涉及到codebase
codebase是一个地址,告诉JVM去哪搜索类,类似于classpath,但classpath是本地路径,而codebase通常是远程URL,比如http,frp等
若我们指定codebase=http://example.com 然后加载org.vulhub.example.Example类,则JVM 会下载这个文件,并作为Example类的字节码
RMI流程中,客户端和服务端之间传递的是一些序列化后的对象,这些对象在序列化时,就会去寻找类,如果某一端反序列化时发现一个对象,那么就会去自己的classpath下寻找对应的类,如果在本地没有找到这个类,就会去远程加载codebase中的类
在RMI中我们是可以将codebase随着序列化数据一起传输,服务器在接受到这个数据后就回去classpath和被控制的codebase寻找类,导致任意命令执行
满足条件
- 安装并配置了securitymanager
- java版本低于7u21,6u45或者设置了Java.rmi.server.useCodebaseOnly=false