Java反序列化-RMI流程分析

RMI 在反序列化里漏洞里面是很常用的,它是一个分布式的思想。

RMI概述

RMI 通常包含两个独立的程序,一个服务端 和 一个客户端。服务端通过绑定这个远程对象类,它可以封装网络操作。客户端层面上只需要传递一个名字,还有地址。

  • 服务端绑定远程对象开了一个动态端口,然后告诉 注册中心,注册中心也有一个端口(默认端口是1099)。
  • 客户端只查找这个名字,注册中心就会告诉他,开了哪个端口,然后回过来找服务端
  • 最后调用服务端绑定的那个名字的接口实现类的某个方法


官方文档:

https://docs.oracle.com/javase/tutorial/rmi/overview.html

代码演示

需要两个主程序,分别为:客户端和服务端。

  • 服务端程序需要实现类和接口
  • 客户端程序只需要接口就好了

服务端

接口类:

package example;

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

public interface IRemoteObj extends Remote {  //客户端有一个接口就行了

    //客户端要调用的方法
    public String sayHello(String keywords) throws RemoteException;


}

接口实现类:

package example;

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


//继承远程对象 UnicastRemoteObject
public class RemoteObjlmpl extends UnicastRemoteObject implements IRemoteObj {

    protected RemoteObjlmpl() throws RemoteException {
    }

    @Override//转大写的功能
    public String sayHello(String keywords) throws RemoteException {
        String upperCase = keywords.toUpperCase();
        System.out.println(upperCase);
        return upperCase;
    }
}

RMIServer 服务端主程序类:

package example;

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

public class RMIServer {
    public static void main(String[] args) throws RemoteException, AlreadyBoundException {
        RemoteObjlmpl remoteObjlmpl = new RemoteObjlmpl(); //new一个实现类
        Registry registry = LocateRegistry.createRegistry(1099); //创建注册中心,它的默认端口为1099
        registry.bind("remoteObj",remoteObjlmpl); //绑定这个实现类的名字为 remoteObj

    }
}

客户端

接口类

package example;

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

public interface IRemoteObj extends Remote {  //客户端有一个接口就行了

    //客户端要调用的方法
    public String sayHello(String keywords) throws RemoteException;


}

客户端主程序类:

package example;

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

public class RMIClient {
    public static void main(String[] args) throws RemoteException, NotBoundException {
        Registry registry = LocateRegistry.getRegistry("127.0.0.1", 1099); //远程获取注册中心的一个连接
        IRemoteObj remoteObj = (IRemoteObj) registry.lookup("remoteObj");//去查找注册中心的这个名字
        remoteObj.sayHello("hello"); //查到了之后,这个接口类型直接调用接口实现类的方法


    }
}

运行

首先再 RMIServer主程序运行,可以看见程序开始监听等待连接
image.png
然后这个时候运行RMIClient 主程序
image.png
这个时候可以看见服务端,成功调用了实现类的方法
image.png

RMI流程

image.png
RMI主要有三部分:

  • 服务端
  • 注册中心
  • 客户端

然后漏洞是产生在 两两通信之间的。

服务端创建远程服务

在这里进行断点调试
image.png
然后来到这里,这是一个静态赋值
image.png
继续按F7,我们来到了这里,远程对象实现类的构造方法
image.png
继续按 F7 就会来到父类的构造函数
image.png
然后接着按F7 我们来到了这里,这个部分会把远程对象发布到一个随机的端口上。可以看见如果我们如果传0,它会发布到一个随机的端口
image.png
然后 按F8来到这里,可以看见调用了这个 exportObject 方法,按F7看一下
image.png
这个方法的大概意思是,导出对象或者发布对象这个意思,很明显它就是一个核心的方法。因为 RemoteObjlmpl 类的构造方法,可以不用继承 UnicastRemoteObject ,也可以直接调用这个 exportObject 静态方法。
image.png
在这里会有处理网络请求的逻辑
image.png
按F7,看一下,点击进到这个方法里
image.png
可以看见它这里又创建了一个 LiveRef 类
image.png
按F7来到这里,点击这个方法名
image.png
按F7,点击 this,看一下构造方法
image.png
可以看见主要有三个参数
image.png
主要看一下 getLocalEndpoint 这个方法,可以看见这个 TCPEndpoint 的意思是处理网络请求大概意思
image.png
可以看见 TCPEndpoint的 构造方法,两个参数,一个 host 、一个怕port。也就是说只要给它一个 ip 一个端口它就可以处理后面的这些网络请求。
使用 LiveRef方法,放的主要是这些
image.png
这个时候返回 LiveRef 方法,看一下它的构造方法
image.png
可以看见有三个参数,真正有意义的是这个ip和端口 进行了封装
image.png
然后按F8来到这里,这里调用了父类的构造方法
image.png
按F7,可以看见它的父类构造方法,这里只是进行了一个赋值,并不是建立了一个新的,还是 liveRef
image.png
然后我们按 F8出来到这里,这里返回了一个方法
image.png
按F7继续调用就来到了这里,sref下有这个 LiveRef
image.png
远程对象还是用 sref 进行了赋值,然后下一步调用了 exportObject 这个方法
image.png
继续按 F7看一下这个方法,可以看见 stub,stub是客户端操作的一个代理。

  • 服务端先创建好了,然后把它放到注册中心上,然后客户端去注册中心拿,拿到再去操作它
  • 通过它stub操作另外一个代理,然后才能真正调用服务端的远程对象

image.png
按F8加F7来到了这里,然后点击。看一下这个方法是怎么创建的
image.png
implClass参数是一个远程对象的实现类,我们自定义的类
image.png
然后这个 clientRef是一个封装的 LiveRef
image.png
按F8来到这里,如果这个 stubClassExists 方法为真
image.png
可以看见 如果 远程对象实现类如果有这个 _Stub结尾的话,结果会返回为真
image.png
F8来到这里,这里是一个创建动态代理的流程
image.png
这个是调用处理器,我们进去看一下
image.png
进去 super方法
image.png
是一个 ref,ref里面还是封装着 LiveRef
image.png
继续按F8可以看见,就已经把动态代理创建好了
image.png
F8来到这里,可以发现是一个总封装,按F7,然后点击 Target
image.png
F8来到这里,重点是这个ID,它和 LiveRef的ID是一样的,总的来说就是把这些东西都放在里面了
image.png
一直按F8来到这里,可以看见 exportObject方法把 target 发布出去
image.png
按F7跟到调用的部分,来到了 TCPTransport类里
image.png

发布远程对象

Listen 实际上我们知道是监听的意思,监听远程对象的某个端口
image.png
这里按F7跟进去,然后按F8来到了这里。可以看见它创建了一个新的sockert,这是一个服务端的socket
image.png
这里开启了一个新的线程,然后等待客户端连接
image.png
继续按F8出去代码逻辑来到这里,可以看见一开始 liveRef的默认端口是0,实际上这里已经随机分配了一个端口了
image.png
查找调用 listen方法,然后找到这里,可以看见 如果 端口为0,它会随机给它一个值,然后返回服务端,实际上服务端已经把这个远程对象发布出去了,但是它把它发送在一个随机的端口上,所以客户端默认是不知道的。
image.png

服务端记录发布

最后服务端还需要记录一下,F8来到这里,然后按F7
image.png
来到这里,继续按F7
image.png
然后就来到了这里,这里是一个简单的赋值,不用管,继续按F8
image.png
然后发现ObjectTable调用了一个 putTarget的方法
image.png
按F7进去,然后一直按F8来到这里,可以看见有两个方法
image.png
这两个方法会把信息保存到这两个 table
image.png
后面一直按F8可以看见已经开始监听网络的线程了,等待客户端进行连接
image.png

服务端创建注册中心

image.png
创建 RegistryImpl 对象,可以看见创建注册中心的默认端口为1099
image.png
来到了注册中心的实现类
image.png
我们来到sref的调用方法

  • uref 是来自 UnicastServerRef类的
  • 也就是说调用 UnicastServerRef类的 exportObject方法

image.png
在 exportObject的方法可以看见参数 permanent的意思为永久,意思是我们创建注册中心这个对象为永久对象
image.png
创建 RegistryImpl_Stub 代理对象,在流程图可以知道它是用作于客户端的代理
image.png
image.png
进去 createStub 方法可以看见,类名的名字改变了,return 返回了加载的初始化 ref
image.png
创建 建 RegistryImpl_Skel 代理对象代理对象。Skeleton 在流程图可以知道它是作于服务端的代理
image.png
image.png
image.png
跟踪方法
image.png
然后f8来到这里,可以发现static中的数据的 objTarget的第二个Target对象的Value的值有一个 DGCImpl_Stub。它是分布式垃圾回收的一个对象,并不是我们创建的,而且这里有三个Target后面会说到。
image.png
至此服务端创建注册中心分析到这里

服务端远程对象绑定创建的注册中心

在这里下断点,可以看见 调用了 Registrylmpl类的方法
image.png
跟进到这里
image.png
实际上 这个 bindings 就是 Hastable表
image.png
至此绑定就分析到这了

注册中心接受并处理服务端的绑定请求

在服务端主程序中进行 DEBUG 调试,然后在这里下一个断点。

  • 注册中心 通过 TCPTransport#handleMessages 处理相关的网络请求

image.png
调用这个方法
image.png
是注册中心的代理,所以走到这个方法里
image.png
再调用这个方法
image.png
可以看见,如果传恶意的 Remote对象,就会存在漏洞。因为需要执行反序列化
image.png
至此,注册中心接受并处理服务端的绑定请求,到这了

客户端获取注册中心代理对象

这里下一个断点
image.png
调用了 getRegistry 方法
image.png
跟踪方法来到这里
image.png
调试到这里
image.png
返回加载创建好的Stub代理对象
image.png
至此客户端获取注册中心代理对象就到这里了

客户端通过注册中心查找远程对象

在这里下断点进行调试
image.png
image.png
image.png
来到invoke方法,再跟踪executeCall方法
image.png
executeCall 主要是处理网络请求的,这个方法中也使用了反序列化方法,也就是说调用invoke,都有可能执行反序列化,如果注册中心是恶意的
image.png

注册中心收到查询请求并返回远程对象代理

这里需要服务端/客户端之间的交互,在服务端主程序进行 DEBUG操作,然后断点在如图的地方。照顾时候运行客户端主程序,由于客户端执行了lookup方法,所以能够进行断点调试。
image.png
然后追踪调试到这里 Transport#disp.disppath
image.png
这里的skel只有注册中心才有,当判断是注册中心就会调用 oldDispath方法,显然这里满足条件了
image.png
进行追踪调试,调用 skel.dispatch 方法
image.png
总的来说是 RegistryImpl_Skel类调用了dispath方法,然后lookup方法中有一个反序列化的点,这里是存在漏洞的
image.png
最后服务端本地调用 RegiistryImpl.lookup(name),获取返回的远程对象,最后远程对象序列化,然后还给客户端,让它进行反序列化读取image.png
至此,注册中心收到查询请求并返回远程对象代理,就到这了。

客户端调用远程对象的方法并获取返回结果

首先服务端运行主程序
image.png
然后客户端在这一行进行下一个断点进行调试
image.png
因为客户端获取的是远程对象动态代理 stub,也就是说它调用任意方法都会走到invoke里
image.png
跟进到重载的invoke()方法里面
image.png
重载的invoke方法里面有一个 marshalValue方法
image.png
这个方法进行了序列化的操作
image.png
实际上 call.executeCall() 方法我们知道执行这个方法是存在漏洞的,客户端如果遇到了恶意的注册中心。
image.png
跟进 unmarshalValue 方法,可以看见最后进行了反序列化的操作
image.png
可以看见之后返回了一个 HELLO的值,成功反序列化的值
image.png
到这一行,返回调用方法执行的结果,至此客户端调用远程对象方法结束。
image.png

服务端接受调用函数请求并返回执行结果

服务端在这里进行下一个断点,然后开启debug进行调试
image.png
这个时候在客户端运行主程序代码
就可以按F8来到这里
image.png
按f9直到 skel为 null的时候按F7步入调试
image.png
image.png
继续往下走
image.png
主要有以下三个关键的点
image.png

第一个关键点

先看第一个 unmarshalValue 方法,最后反序列化客户端序列化的内容
image.png
因为要反序列化数据的类型是String,所以它绕过了前面的判断
image.png
可以看见反序列化参数成功了
image.png

第二个关键点

再看第二个关键点,当服务端进行反射调用后,可以看见方法执行成功并且返回了值
image.png

第三个关键点

跟进到 marshalValue方法,可以看见它是进行序列化返回值的操作
image.png
至此可以看见客户端进程直接运行完毕了,因为它收到了来自服务端发送的返回值。
image.png
服务端完成接受客户端的调用、执行本地函数、返回执行结果的过程就是这样了。

客户端请求服务端-dgc

DGC代理的产生

在这里下断点进行调试
image.png
F8来到这里,可以发现stub 是 dgc代理
image.png
来到DGCImpl的实现类进行断点调试
image.png
至此DGC_Stub的创建就完成了,DGC是一个自动创建的过程,用于清理内存

DGC实现类Stub

DGCImpl_Stub 的类下有两个方法,一个是clean(强清除)、一个是dirty(弱清除)。
image.png
在clean方法中存在反序列化的漏洞点

DGC实现类Skel

在DGCImpl_Skel类中的dispath方法中,存在反序列化漏洞的入口
image.png

总结

漏洞点在客户端与服务端都存在,因为Skel代理是服务端,Stub代理是客户端。所以这就是JRMP所谓的绕过。

参考链接:

https://jaspersec.top/2023/12/24/0x0A%20RMI%E6%BC%8F%E6%B4%9E%E5%88%86%E6%9E%90/#%E5%AE%A2%E6%88%B7%E7%AB%AF%E6%94%BB%E5%87%BB%E6%9C%8D%E5%8A%A1%E7%AB%AF
https://www.bilibili.com/video/BV1L3411a7ax/?p=8&spm_id_from=pageDriver&vd_source=9f847c5239350d8425b1d2242ef00bbf
https://drun1baby.github.io/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/

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/675571.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

LNMP 环境下使用 Zstd 压缩优化网站备份脚本

网站的备份一直都是网站运营、服务器运维中很重要的一环,明月无论是在自己的服务器还是客户的代运维服务器上都是非常重视网站备份的,尤其热衷于优化网站备份这块儿,毕竟明月自己的服务器配置一直都是最低的 1H1G 呀,就这配置常年…

(函数)空格填充(C语言)

一、运行结果&#xff1b; 二、源代码&#xff1b; # define _CRT_SECURE_NO_WARNINGS # include <stdio.h>//声明空格填充函数&#xff1b; void space(char a[100]);int main() {//初始化变量值&#xff1b;char a[100] { 0 };//获取用户输入的数据&#xff1b;print…

人工智能学习笔记(1):了解sklearn

sklearn 简介 Sklearn是一个基于Python语言的开源机器学习库。全称Scikit-Learn&#xff0c;是建立在诸如NumPy、SciPy和matplotlib等其他Python库之上&#xff0c;为用户提供了一系列高质量的机器学习算法&#xff0c;其典型特点有&#xff1a; 简单有效的工具进行预测数据分…

Kafka生产者消息异步发送并返回发送信息api编写教程

1.引入依赖&#xff08;pox.xml文件&#xff09; <dependencies> <dependency> <groupId>org.apache.kafka</groupId> <artifactId>kafka-clients</artifactId> <version>3.6.2</version> </dependency> </depende…

泰中完成潜艇采购谈判:有三个亮点值得关注

泰中完成潜艇采购谈判&#xff1a;有三个亮点值得关注 --如何写好产品采购制造类合同&#xff08;或协议&#xff09;才能有效维护你的利益 据新华网消息&#xff1a;泰国和中国已经完成关于泰国皇家海军购买中国制造的S26T元级潜艇的谈判。这份交易正在等待内阁的最终批准。…

WALT算法简介

WALT(Windows-Assist Load Tracing)算法是由Qcom开发&#xff0c; 通过把时间划分为窗口&#xff0c;对 task运行时间和CPU负载进行跟踪计算的方法。为任务调度、迁移、负载均衡及CPU调频 提供输入。 WALT相对PELT算法&#xff0c;更能及时反映负载变化&#xff0c; 更适用于…

黄金期货与现货黄金有什么区别?

如今&#xff0c;黄金成为了众多投资者的热门选择。在黄金投资市场中&#xff0c;有多种形式可供选择&#xff0c;比如纸黄金、实物黄金、黄金期货以及现货黄金等。其中&#xff0c;纸黄金和实物黄金虽然具有其特点&#xff0c;但所需资金量较大且收益表现相对不明显。相对而言…

哈希重要思想续——布隆过滤器

布隆过滤器 一 概念1.1布隆过滤器的提出2.概念 二 模拟实现2.1 三个仿函数setTest 全代码三 实际应用 一 概念 1.1布隆过滤器的提出 我们在使用新闻客户端看新闻时&#xff0c;它会给我们不停地推荐新的内容&#xff0c;它每次推荐时要去重&#xff0c;去掉那些已经看过的内容…

Java面试八股之守护线程和普通线程的区别

守护线程和普通线程的区别 生命周期差异&#xff1a; 普通线程&#xff08;也称为用户线程&#xff09;&#xff1a;这类线程的生命周期与程序的生命周期独立。它们会一直运行直到完成自己的任务或主动结束&#xff0c;如果一个程序中只剩下普通线程在运行&#xff0c;即使主…

JavaScript、Kotlin、Flutter可以开发鸿蒙APP吗?

自从去年华为宣布推出「鸿蒙Next」版本开始&#xff0c;标志着其操作系统的全面革新。鸿蒙Next将摒弃所有基于AOSP的代码&#xff0c;与Android系统彻底分离&#xff0c;实现完全自主的研发路径。通过精简约40%的冗余代码&#xff0c;鸿蒙Next致力于构建一个更高效、更流畅的系…

混合动力电动汽车介绍(二)

接续前一章内容&#xff0c;本篇文章介绍混合动力汽车串联、并联和混联的系统组成和工作原理。 一、串联混合动力电动汽车的系统组成和工作原理 上图为串联混合动力电动汽车的结构简图。汽车由电动机-发电机驱动行驶&#xff0c;电机控制器的动力来自油箱-发动机-发电机-发电机…

Python画图(多图展示在一个平面)

天行健&#xff0c;君子以自强不息&#xff1b;地势坤&#xff0c;君子以厚德载物。 每个人都有惰性&#xff0c;但不断学习是好好生活的根本&#xff0c;共勉&#xff01; 文章均为学习整理笔记&#xff0c;分享记录为主&#xff0c;如有错误请指正&#xff0c;共同学习进步。…

上位机图像处理和嵌入式模块部署(f407 mcu vs f103)

【 声明&#xff1a;版权所有&#xff0c;欢迎转载&#xff0c;请勿用于商业用途。 联系信箱&#xff1a;feixiaoxing 163.com】 对于一部分嵌入式场景来说&#xff0c;f103其实已经足够了&#xff0c;特别是要求不高的低速场合。如果开发的代码比较多&#xff0c;还可以选用更…

Java面试八股之线程池中submit和execute方法的区别

线程池中submit和execute方法的区别 接口和返回值类型: execute()方法属于Executor接口&#xff0c;它接收一个实现了Runnable接口的任务&#xff0c;并不返回任何结果。它的主要目的是异步执行任务&#xff0c;不关心任务的执行结果。 submit()方法则是ExecutorService接口…

Vue渲染函数与JSX指南

title: Vue渲染函数与JSX指南 date: 2024/6/3 下午6:43:53 updated: 2024/6/3 下午6:43:53 categories: 前端开发 tags:Vue渲染JSX基础性能优化组件对比React JSX大项目测试策略 第1章&#xff1a;Vue.js入门 Vue.js的历史和背景 Vue.js是一个用于构建用户界面的JavaScript框…

模拟堆-java

模拟堆也是对堆的一次深入理解和一些其它操作&#xff0c;可以了解一下。 文章目录 前言 一、模拟堆 二、算法思路 1.结点上移 2.结点下移 3.插入一个数 4.输出当前集合的最小值 5.删除当前集合的最小值&#xff08;数据保证此时的最小值唯一&#xff09; 6.删除第k个插入的数 …

初识STM32单片机-ADC和DMA

初识STM32单片机-ADC和DMA 一、ADC(模拟数字转换器)简介二、ADC基本结构三、DMA(直接存储器读取)简介四、DMA框图和基本结构五、DMA应用实例5.1 数据转运DMA5.2 ADC扫描DMA 六、程序编码6.1 ADC单通道-电位器6.2 ADC多通道-电位器和光敏\热敏\反射红外传感器6.3 DMA数据转运6.4…

代码随想录算法训练Day28|LeetCode93-复原IP地址、LeetCode78-子集问题、LeetCode90-子集2

复原IP地址 题目描述 力扣93-复原IP地址 有效 IP 地址 正好由四个整数&#xff08;每个整数位于 0 到 255 之间组成&#xff0c;且不能含有前导 0&#xff09;&#xff0c;整数之间用 . 分隔。 例如&#xff1a;"0.1.2.201" 和 "192.168.1.1" 是 有效 …

贝锐花生壳DDNS:远程访问数据库,仅需简单3步

在当今数字化时代&#xff0c;数据的远程访问和管理变得至关重要。无论是企业还是个人开发者&#xff0c;都需要一种简单、安全的方式来远程访问和管理本地部署的数据库&#xff0c;如MySQL、PostgreSQL、MongoDB等。贝锐花生壳DDNS服务提供了一个完美的解决方案&#xff0c;通…

【YOLOv10改进[Backbone]】图像修复网络AirNet助力YOLOv10目标检测效果 + 含全部代码和详细修改方式 + 手撕结构图 + 全网首发

本文带来的是图像复原网络AirNet&#xff0c;它由基于对比度的退化编码器( CBDE )和退化引导的恢复网络( DGRN )两个模块组成。可以在一个网络中恢复各种退化图像。AirNet不受损坏类型和级别的先验限制&#xff0c;仅使用观察到的损坏图像进行推理。本文中将使用图像修复网络Ai…