当前位置: 首页 > news >正文

【Java安全】RMI基础

文章目录

    • 介绍
    • 实现
      • 服务端 Server
      • 客户端 Client
    • 通信过程
      • 数据端与注册中心(1099 端口)建立通讯
      • 客户端与服务端建立 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

定义远程接口

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); }@Overridepublic 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/
http://www.lryc.cn/news/582667.html

相关文章:

  • navicat导出数据库的表结构
  • 【1-快速上手】
  • .NET9 实现对象深拷贝和浅拷贝的性能测试
  • ROS平台上使用C++实现A*算法
  • TensorFlow深度学习实战——基于自编码器构建句子向量
  • 微服务集成snail-job分布式定时任务系统实践
  • Go语言反射机制详解
  • 手动实现 Tomcat 核心机制:打造属于自己的 Servlet 容器
  • 【AI智能体】智能音视频-硬件设备基于 WebSocket 实现语音交互
  • 一文讲清楚React的diff算法
  • 汽车功能安全系统阶段开发【技术安全方案TSC以及安全分析】5
  • Eigen中Isometry3d的使用详解和实战示例
  • UDP的socket编程
  • 黑马点评系列问题之P37商户点评缓存作业,用了string和list两种方法,可以直接复制粘贴
  • 微软上线Deep Research:OpenAI同款智能体,o3+必应双王炸
  • 专题:2025数据资产AI价值化:安全、战略与应用报告|附400+份报告PDF、原数据表汇总下载
  • openEuler2203sp4-vg磁盘组中剔除磁盘
  • 香港站群服务器与普通香港服务器对比
  • Windows 系统安装与使用 Claude Code 全攻略
  • 【LeetCode 热题 100】142. 环形链表 II——快慢指针
  • OpenWebUI(4)源码学习-后端routers路由模块
  • 车载以太网-TC8测试-UT(Upper Tester)
  • C语言使用Protobuf进行网络通信
  • 2025年微软mos备考攻略-穷鬼版
  • claude code-- 基于Claude 4 模型的智能编程工具,重塑你的编程体验
  • 【DOCKER】-2 docker基础
  • 字符串大小比较的方式|函数的多返回值
  • python transformers库笔记(BertForTokenClassification类)
  • BM10 两个链表的第一个公共结点
  • Linux_常见指令和权限理解