【Java安全】RMI基础

文章目录

    • 介绍
    • 实现
      • [服务端 Server](#服务端 Server)
      • [客户端 Client](#客户端 Client)
    • 通信过程
      • [数据端与注册中心(1099 端口)建立通讯](#数据端与注册中心(1099 端口)建立通讯)
      • [客户端与服务端建立 TCP 通讯](#客户端与服务端建立 TCP 通讯)
      • [客户端序列化传输 调用函数的输入参数至服务端](#客户端序列化传输 调用函数的输入参数至服务端)
      • 总结

介绍

RMI 全称 Remote Method Invocation(远程方法调用),即在一个 JVM 中 Java 程序调用在另一个远程 JVM 中运行的 Java 程序,这个远程 JVM 既可以在同一台实体机上,也可以在不同的实体机上,两者之间通过网络进行通信。

RMI的一般要用到的组件:

  • Remote Interface:远程接口

需要定义一个接口,继承自 java.rmi.Remote,表明可以被远程对象调用的方法。

远程调用可能发生网络异常 , 所以每个方法都必须显式抛出 RemoteException

  • Remote Object Implementation:远程接口的具体实现

一般需要继承UnicastRemoteObject类, 将对象导出成一个 可以通过 TCP 调用的远程对象

  • Server:服务端,注册远程对象到 RMI 注册中心。
  • Client:客户端,查找远程对象并调用其方法。
  • Registry:注册端提供服务注册与服务获取。即 Server 端向 Registry 注册服务,比如地址、端口等一些信息,Client 端从 Registry 获取远程对象的一些信息,如地址、端口等,然后进行远程调用。

实现

服务端 Server

定义远程接口

java 复制代码
package RMI.Server;

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

public interface Hello extends Remote {
    public String sayHello(String name) throws RemoteException;
}

远程接口的实现

java 复制代码
package RMI.Server;

import java.rmi.RemoteException;
import java.rmi.server.UnicastRemoteObject;

public class HelloImpl extends UnicastRemoteObject implements Hello {
    public HelloImpl() throws RemoteException {
        super(); //也可以什么都不写,隐式调用
        //如果没有继承UnicastRemoteObject,就需要手动导出: UnicastRemoteObject.exportObject(this, 0); 
    }
    @Override
    public String sayHello(String name) throws RemoteException {
        return "Hello " + name;
    }
}

服务端

主要是创建 RMI 注册表(使用默认端口 1099),创建服务实现类的实例,将远程对象绑定到注册表中

java 复制代码
package RMI.Server;

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

public class RMIServer {
    public static void main(String[] args) {
        try{
            //实例化远程对象
            HelloImpl obj = new HelloImpl();

            //启动本地的RMI注册服务(一般默认 1099 端口),创建注册中心
            LocateRegistry.createRegistry(1099);
            Registry registry = LocateRegistry.getRegistry();

            //绑定远程对象
            registry.bind("HelloImpl", obj);
            //或者import java.rmi.Naming;
            //Naming.bind("rmi://127.0.0.1:1099/HelloImpl", obj);

        }catch (Exception e){
            e.printStackTrace();
        }

    }
}

客户端 Client

连接到本地(localhost)的 RMI 注册表然后查找相应名字的远程对象,最后调用远程方法,传入相应参数

java 复制代码
package RMI.Client;

import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
import RMI.Server.Hello; // 导入服务器端的远程接口


public class RMIClient {
    public static void main(String[] args) throws Exception {
        //连接到服务器
        Registry registry = LocateRegistry.getRegistry("localhost", 1099);

        //通过名字查找远程对象
        Hello hello = (Hello) registry.lookup("HelloImpl");
        //调用远程对象上面的方法
        String response = hello.sayHello("xpw");
        System.out.println("response :"+response);
    }

}

先运行服务端, 再运行客户端, 在客户端就可以看到调用了远程对象的方法了

通信过程

很多复制粘贴的来自其他师傅的博客,了解了一下内部通信的知识,还没有动手去尝试抓包

数据端与注册中心(1099 端口)建立通讯

  • 客户端查询需要调用的函数的远程引用,注册中心返回远程引用和提供该服务的服务端 IP 与端口。

客户端与注册中心(1099 端口)建立通讯完成后,客户端 向注册中心发送了⼀个 "Call" 消息,注册中心回复了⼀个 "ReturnData" 消息,然后客户端新建了⼀个 TCP 连接,连到服务端的 33769 端⼝

AC ED 00 05是常见的 Java 反序列化 16 进制特征

注意以上两个关键步骤都是使用序列化语句

客户端与服务端建立 TCP 通讯

客户端发送远程引用给服务端,服务端返回函数唯一标识符,来确认可以被调用

同样使用序列化的传输形式

以上两个过程对应的代码是这两句

java 复制代码
Registry registry = LocateRegistry.getRegistry("127.0.0.1", 1099);  
RemoteObj remoteObj = (RemoteObj) registry.lookup("remoteObj"); // 查找远程对象

这里会返回一个 Proxy 类型函数,这个 Proxy 类型函数会在我们后续的攻击中用到。

客户端序列化传输 调用函数的输入参数至服务端

  • 这一步的同时:服务端返回序列化的执行结果至客户端

以上调用通讯过程对应的代码是这一句

复制代码
remoteObj.sayHello("hello");

可以看出所有的数据流都是使用序列化传输的,那必然在客户端和服务带都存在反序列化的语句。

总结

整个过程进⾏了两次TCP握⼿,也就是我们实际建⽴了两次 TCP连接。

第⼀次建⽴TCP连接是连接远端 ip 的1099端⼝,这也是我们在代码⾥看到的端⼝,⼆ 者进⾏沟通后,我向远端发送了⼀个"Call"消息,远端回复了⼀个"ReturnData"消息,然后我新建了⼀ 个TCP连接,连到远端的33769端⼝。

之所以是33769端口, 因为在"ReturnData"这个包中,返回了⽬标的IP地址,其后跟的⼀个字节 \x00\x00\x83\xE9 ,刚好就是整数 33769 的网络序列

所以捋一下整个的过程: 首先客户端连接Registry,并在其中寻找Name是HelloImpl的对象,这个对应数据流中的Call消息;然后Registry返回⼀个序列化的数据,这个就是找到的Name=HelloImpl的对象,这个对应数据流中的ReturnData消息;客户端反序列化该对象,发现该对象是⼀个远程对象,地址在 127.0.0.1:33769 ,于是再与这个地址建⽴TCP连接;在这个新的连接中,才执⾏真正远程 ⽅法调⽤,也就是 HelloImpl()

各个元素之间的关系

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

参考文章

java 复制代码
代码审计社区 Java安全漫谈
https://drun1baby.top/2022/07/19/Java%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%E4%B9%8BRMI%E4%B8%93%E9%A2%9801-RMI%E5%9F%BA%E7%A1%80/#Java-%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%E4%B9%8B-RMI-%E4%B8%93%E9%A2%98-01-RMI-%E5%9F%BA%E7%A1%80
https://fushuling.com/index.php/2023/01/30/java%e5%ae%89%e5%85%a8%e7%ac%94%e8%ae%b0/
相关推荐
wellc33 分钟前
SpringBoot集成Flowable
java·spring boot·后端
Hui Baby1 小时前
springAi+MCP三种
java
hsjcjh1 小时前
【MySQL】C# 连接MySQL
java
敖正炀1 小时前
LinkedBlockingDeque详解
java
wangyadong3171 小时前
datagrip 链接mysql 报错
java
untE EADO1 小时前
Tomcat的server.xml配置详解
xml·java·tomcat
ictI CABL2 小时前
Tomcat 乱码问题彻底解决
java·tomcat
敖正炀2 小时前
DelayQueue 详解
java
Y学院2 小时前
虚拟机安装ParrotOS完整教程(VMware+VirtualBox双版本)
安全·网络安全
敖正炀2 小时前
PriorityBlockingQueue 详解
java