首先搞明白如下问题:

  1. RMI是什么?
  2. 它和安全有什么关系?

    RMI

    RMI全称是,Remote Method Invocation,远程方法调用,它的功能是可以让jvm虚拟机A调用远程虚拟机B中对象上的方法.

RMI存在的意义?
image.png

RMI技术方面实现?

  1. RMIServer: 远程服务器,需要实现具体的Java方法并提供接口

RMIServer一般又包括下面这两个部分:

  1. 方法的实现: 真正意义上的”RMIServer”, 提供远程的对象实现.
  2. 方法的注册: RMI Registry,注册中心, 需要标记远程对象的位置.
    1. RMIClient: 本地客户端,需要提供接口的定义

代码的实现?

  1. 首先实现远程对象类的接口: ```java package test.java;

import java.rmi.Remote; import java.rmi.RemoteException;

public interface RMIHelloInterface extends Remote { String hello() throws RemoteException; }

  1. 接口需要继承Remote接口,然后声明需要远程调用的方法.
  2. > 为什么要实现接口,不实现行不行?
  3. > 1. 不行, 不实现怎么给客户端调用?
  4. 2. 实现具体的远程对象类
  5. 实现接口的同时需要继承UnicastRemoteObject
  6. ```java
  7. package test.java;
  8. import java.rmi.Remote;
  9. import java.rmi.RemoteException;
  10. import java.rmi.server.UnicastRemoteObject;
  11. public class RMIHelloImpl extends UnicastRemoteObject implements Remote {
  12. protected RMIHelloImpl() throws RemoteException {
  13. super();
  14. }
  15. public String hello() throws RemoteException {
  16. System.out.println("hello called!");
  17. return "this is hello";
  18. }
  19. }
  1. 实现具体的Server,

Server要做的就是实例化远程对象并绑定到端口和name上.

  1. package test.java;
  2. import java.rmi.Naming;
  3. import java.rmi.Remote;
  4. import java.rmi.registry.LocateRegistry;
  5. public class RMIServer {
  6. public static void main(String[] args){
  7. try {
  8. // 创建一个RMI的注册中心
  9. LocateRegistry.createRegistry(1234);
  10. // 实例化RMI服务对象
  11. Remote hello = new RMIHelloImpl();
  12. // 绑定
  13. Naming.rebind("rmi://127.0.0.1:1234/hello",hello);
  14. } catch (Exception e){
  15. e.printStackTrace();
  16. }
  17. }
  18. }
  1. Client调用: ```java package test.java;

import java.rmi.Naming; import java.rmi.registry.LocateRegistry; import java.rmi.registry.Registry;

public class RMIClient { public static void main(String[] args) { try { Registry registry = LocateRegistry.getRegistry(“127.0.0.1”,1234); // 获取所有注册的服务 String[] list = registry.list(); for(String i : list) { System.out.println(“service: “ + i); }

  1. RMIHelloInterface hello = (RMIHelloInterface) Naming.lookup("rmi://127.0.0.1:1234/hello");
  2. hello.hello();
  3. } catch (Exception e) {
  4. e.printStackTrace();
  5. }
  6. }

} ```

RMI 通信

可以参考 http://werner.yellowcouch.org/Lectures/javarmi/
通信的流程如下:
image.png

  1. 把Called Object注册到Registry
  2. Client连接Registry地址,然后发送一个Call消息
  3. Registry回复一个ReturnData消息,这个消息包含要调用对象的地址和端口
  4. Client再去连接远程调用对象的地址,然后远程对象返回执行结果

在第四步里面,涉及了一些序列化和反序列化:

具体的细节先不看,这些点都是攻击面

image.png

然后在这篇文章中讲解的序列化的点和反序列化的点很详细了: https://myzxcg.com/2021/10/Java-RMI%E5%88%86%E6%9E%90%E4%B8%8E%E5%88%A9%E7%94%A8/
image.png

  1. Server到RMIRegistry(步骤二)
  2. Client与RMIRegistry的双向通信(步骤三,四)
  3. Client调用远程方法(步骤六,九)

通信过程梳理:

  1. 客户端⾸先连接Registry,并在其中寻找Name是hello的对象。然后Registry返回一个序列化的数据(Hello对象的初始引用)
  2. 这个hello对象是由一个动态代理生成的类,包含与Server通信的IP和端口。Client与该地址进行连接,在该连接中才真正调用远程⽅法。
  3. 实际的代码是在RMIServer中执行.

    攻击面

    攻击面的话就是各个反序列化的点和通信的消息.