【Java安全】RMI基础

发布于:2025-07-09 ⋅ 阅读:(18) ⋅ 点赞:(0)

介绍

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

定义远程接口

package RMI.Server;

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

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

远程接口的实现

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),创建服务实现类的实例,将远程对象绑定到注册表中

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 注册表然后查找相应名字的远程对象,最后调用远程方法,传入相应参数

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 通讯

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

在这里插入图片描述

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

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

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安全漫谈
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/

网站公告

今日签到

点亮在社区的每一天
去签到