RMI

概念

RMI的过程,就是用JRMP协议去组织数据格式,然后通过TCP进行传输,从而达到远程方法调用。

  • RMI(Remote Method Invocation):远程方法调用。即让一个JVM中的对象远程调用另一个JVM中的对象的某个方法,简单来说就是跨越JVM,使用Java调用远程Java程序
    • Server服务端:提供远程的对象
    • Client客户端:调用远程的对象
    • Registry注册表:存放着远程对象的位置,用于客户端查询所调用的远程方法的引用

需要注意的是:被调用的方法实际上是在RMI服务端执行

  • JRMP(Java Remote Message Protocol):Java 远程消息交换协议。运行在TCP/IP之上的线路层协议,该协议要求服务端与客户端都为Java编写。

    • Java本身对RMI规范的实现默认使用JRMP协议,而在Weblogic中使用T3协议
  • JNDI(Java Naming and Directory Interface):Java命名和目录接口。一组在Java应用中访问命名和目录服务的接口,Java中使用最多的基本就是RMI和LDAP的目录服务系统,客户端可以通过名称访问对象,并将其下载下来。

    • 命名服务:将名称和对象联系起来,客户端可以使用名称访问对象
    • 目录服务:一种命名服务,在命名服务的基础上,增加了属性的概念

RMI-Server

  • RMI Server分为三部分:
    • 一个远程接口。继承java.rmi.Remote,其中定义要远程调用的函数。
    • 远程接口的实现类。继承java.rmi.server.UnicastRemoteObject,实现远程调用的函数
    • 创建实例和Registry注册表,然后在注册表中绑定地址和实例

定义远程接口

  • 定义一个远程接口,继承java.rmi.Remote接口,抛出RemoteException异常,修饰符需要为public否则远程调用的时候会报错 ```java package com.naraku.sec.rmidemo;

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

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

  1. <a name="Wlrij"></a>
  2. ### 远程接口实现
  3. - 远程接口的实现类,继承`java.rmi.server.UnicastRemoteObject`,实现远程调用的函数
  4. ```java
  5. package com.naraku.sec.rmidemo;
  6. import java.rmi.RemoteException;
  7. import java.rmi.server.UnicastRemoteObject;
  8. public class RemoteHelloWorld extends UnicastRemoteObject implements IRemoteHelloWorld {
  9. protected RemoteHelloWorld() throws RemoteException {
  10. super();
  11. System.out.println("RemoteHelloWorld构造方法");
  12. }
  13. @Override
  14. public String hello() throws RemoteException {
  15. System.out.println("Callback");
  16. return "Hello";
  17. }
  18. }

创建实例和注册表

  • Naming.bindNaming.rebind的区别:

    • bind指“绑定”,如果时“绑定”时Registry已经存在对应的Name,则系统会抛出错误
    • rebind指“重绑定”,如果“重绑定”时Registry已经存在对应的Name,则绑定的远程对象将被替换
    • 除非有特别的业务需求,否则建议使用rebind方法进行绑定
  • Registry.rebindNaming.rebind的区别:

    • Registry.rebind是使用RMI注册表绑定,所以不需要完整RMI URL
    • Naming.rebind是通过Java的名称服务进行绑定,由于名称服务不止为RMI提供查询服务,所以绑定时需要填入完整RMI URL

Naming.rebind

  • 实现Registry并将上面的类实例化,然后绑定到一个地址
  1. package com.naraku.sec.rmidemo;
  2. import java.rmi.Naming;
  3. import java.rmi.registry.LocateRegistry;
  4. public class DemoServer {
  5. public static void main(String[] args) throws Exception {
  6. // 实例化远程对象
  7. RemoteHelloWorld hello = new RemoteHelloWorld();
  8. // 实现注册表
  9. LocateRegistry.createRegistry(1099);
  10. // 将远程对象注册到注册表里面, 绑定地址
  11. Naming.rebind("rmi://192.168.111.1:1099/Hello", hello);
  12. // 如果Registry在本地, Host和Port可以省略, 默认 localhost:1099
  13. // Naming.rebind("Hello", hello);
  14. System.out.println("Start Server, Port is 1099");
  15. }
  16. }

Registry.rebind

  1. // 创建远程对象
  2. RemoteHelloWorld hello = new RemoteHelloWorld();
  3. // 创建注册表
  4. Registry registry = LocateRegistry.createRegistry(1099);
  5. // 将远程对象注册到注册表里面,绑定地址
  6. registry.rebind("Hello",hello);

RMI-Client

  • 编写RMIClient,并调用远程对象。需要注意的是,如果远程方法有参数,调用方法时所传入的参数必须是可序列化的。在传输中是传输序列化后的数据,服务端会对客户端的输入进行反序列化
  1. package com.naraku.sec.rmidemo;
  2. import java.net.MalformedURLException;
  3. import java.rmi.Naming;
  4. import java.rmi.NotBoundException;
  5. import java.rmi.RemoteException;
  6. public class DemoClient {
  7. public static void main(String[] args) throws RemoteException, NotBoundException, MalformedURLException {
  8. // 查询 hello 对象
  9. IRemoteHelloWorld hello = (IRemoteHelloWorld) Naming.lookup("rmi://192.168.111.2:1099/Hello");
  10. // 调用远程方法
  11. String ret = hello.hello();
  12. System.out.println(ret);
  13. }
  14. }
  • Client也有Registry.lookupNaming.lookup,但它们是一样的

RMI通信过程

Registry就像⽹关,不会执⾏远程⽅法,但Server可以在上⾯注册⼀个Name到对象的绑定关系。Client通过Name向Registry查询,得到这个绑定关系,然后再连接Server。最后,远程⽅法在Server上调⽤。

发起RMI通信

发起一次RMI通信,Server端192.168.111.1,Client端192.168.111.2
image.png

通信过程中会建立2次TCP连接,数据包附件在文末
image.png

第1次连接是Client和Registry的连接,连接到目标IP:1099

  • Client发送Call消息:Client连接Registry,寻找Name=Hello的对象

image.png

  • Registry响应ReturnData消息:返回Name=Hello对象的序列化数据,并包含对象的IP和端口
    • 0xACED0005常见的Java反序列化16进制特征。所以这里从\xAC\xED开始就是序列化的数据,IP和端口这只是这个对象的一部分

image.png

  • 返回的端口位于IP地址后一个字节,这里是\x00\x00\xe0\x7757463,所以后面Client将向Server的该端口发起第2次请求 ```python int(“0x0000e077”, 16)
  1. 2次连接是ClientServer的连接。ClientServer的目标端口发起请求,并正式调用远程方法
  2. <a name="EvY2h"></a>
  3. ### 远程调用报错
  4. - ClientServer`package`路径需要一致
  5. ![image.png](https://cdn.nlark.com/yuque/0/2022/png/520228/1648456791641-1c5e9de3-13ef-4073-b826-dbd8d7b89219.png#clientId=u0ba54ed4-a501-4&from=paste&height=136&id=jXhPN&margin=%5Bobject%20Object%5D&name=image.png&originHeight=271&originWidth=1211&originalType=binary&ratio=1&size=64223&status=done&style=none&taskId=u3a094694-8fdc-407e-b020-b98f646b32a&width=605.5)
  6. <a name="E967h"></a>
  7. ## 攻击RMI Registry
  8. - 前面是RMI整个的原理与流程,那么RMI会带来哪些安全问题?
  9. - 如果我们能访问RMI Registry服务,如何对其攻击?
  10. - 如果我们控制了目标RMI客户端中`Naming.lookup`的第一个参数(也就是RMI Registry的地址),能不能进行攻击?
  11. - 这里尝试在`192.168.111.2`中调用`192.168.111.1`Registry服务,发现报错如下图所示。原因是**Java对远程访问RMI Registry做了限制**,只有来源地址是`localhost`的时候,才能调用`rebind/bind/unbind`等方法。
  12. > 官方文档:出于安全原因,应用程序只能绑定或取消绑定到在同一主机上运行的注册中心。这样可以防止客户端删除或覆盖服务器的远程注册表中的条目。但是,查找操作是任意主机都可以进行的。
  13. ![image.png](https://cdn.nlark.com/yuque/0/2022/png/520228/1648525066896-cf539a7a-6438-4bdb-bb69-4fb33589a1d9.png#clientId=u39f2d539-ef97-4&from=paste&height=372&id=u1070f5ac&margin=%5Bobject%20Object%5D&name=image.png&originHeight=743&originWidth=1052&originalType=binary&ratio=1&size=394382&status=done&style=none&taskId=u19b656c0-5ef5-4b80-a8f9-de7e017209d&width=526)
  14. - 但可以远程调用`list/lookup`方法,`list`方法可以列出目标上所有绑定的对象
  15. ```java
  16. package com.naraku.sec.rmidemo;
  17. import java.net.MalformedURLException;
  18. import java.rmi.Naming;
  19. import java.rmi.NotBoundException;
  20. import java.rmi.RemoteException;
  21. public class DemoClient {
  22. public static void main(String[] args) throws RemoteException, NotBoundException, MalformedURLException {
  23. // list方法
  24. String[] objs = Naming.list("rmi://192.168.111.1:1099/");
  25. for (String obj:objs) {
  26. System.out.println(obj);
  27. }
  28. }
  29. }

image.png