Java反序列化-RMI流程分析

Java反序列化-RMI流程分析

概述

官方文档:https://docs.oracle.com/javase/tutorial/rmi/overview.html

RMI应用程序通常由两个独立的程序组成,一个服务器和一个客户端。服务端通过绑定这个远程对象类,它可以封装网络操作。客户端层面上只需要传递一个名字,还有地址。RMI提供了服务器和客户端通信和来回传递信息的机制。这种应用程序有时称为分布式对象应用程序。

  • RMI服务端:负责“暴露远程对象+处理调用请求”

  • 服务端的核心目标是将可被远程调用的对象注册到注册表中,并监听客户端的调用请求,最终执行方法并返回结果。

  • RMI客户端:负责“查找远程对象+发起远程调用”

  • 核心目的是找到服务端注册表中的远程对象,获取其本地代理(stub),并通过代理调用远程方法。

1759043624880-47dc8427-e594-4798-a2b9-1e0332b47bcd.gif

代码演示

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

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

公共接口类

package org.example;


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

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

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

服务端

接口实现类

package org.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 org.example;


import java.rmi.AlreadyBoundException;
import java.rmi.NotBoundException;
import java.rmi.RemoteException;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
import java.rmi.server.UnicastRemoteObject;

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

        System.out.println("Server ready");
    }
}

客户端

客户端主程序类

package org.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主程序运行,可以看到程序开始监听等待连接

1759068307425-6bdbab5c-ad76-4f31-ae85-4aa263d322b3.png

然后这个时候运行RMIClient主程序

1759068394285-2a7dee3f-98d6-4752-8f0f-27b05dfe6ec7.png

这个时候看见服务端,成功调用了实现类的方法

RMI流程总览

1759068519328-b728ba92-f44e-42b6-954c-f86dd9c7b3a7.png

RMI主要有三部分

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

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

RMI流程

从wireshark抓包分析RMI通信原理

下载Npcap:https://npcap.com/#download,在安装时选择Support loopback traffic

再打开wireshark时就会看到一个新接口Npcap Loopback Adapter

选择该接口开始抓包就可以抓取127.0.0.1的流量了。

在客户端远程调Java程序的过程中其实建立了两次TCP连接,第一次连接是连接1099端口;第二次连接是由服务端发送给客户端的。

  • 第一次连接:客户端来连接注册中心(Registry)在其中寻找调用函数的名称,这个对应数据流中的Call消息,然后注册中心返回一个序列化的数据,这个就是找到的Name=调用函数的名称的对象,这个对应数据流中的ReturnData消息

call消息

1759132828980-9a62da6f-1d29-4fde-9d32-646fbe5d69da.png

ReturnData消息

1759133965597-18a9f9b8-a519-480b-a83d-3259718bdef0.png

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

  • 第二次连接:服务端发送给客户端的Call消息客户端反序列化对象,发现该对象是一个远程对象,地址在ip:port,于是再与这个地址建立TCP连接;在这个新的连接中,才执行真正的远程调用,也就是sayHello()

总的来说,RMI Registry就像一个网关,他自己是不会执行远程方法的,但RMI Server可以在上面注册一个Name到对象的绑定关系;RMI Client通过Name向RMI Registry查询,得到这个绑定关系,然后再连接RMI Server。最后,远程方法实际上在RMI Server上调用。

1759139652228-c692a85c-ede0-4d69-9578-ce3db6b3b357.png

断点调试理解

创建远程服务

在这里进行断点调试

1759147088054-d040141e-1603-4a8b-86a1-607c85aa561c.png

发布远程对象

这一行代码的主要功能是在本地实例化远程对象并把它发布到网络上

RemoteObjlmpl remoteObjlmpl = new RemoteObjlmpl();    //new一个实现类

一步步调试查看它的内在逻辑

来到这里,远程对象实现类的构造方法

1759125774108-1a2f1f7c-7e9e-41f0-8761-328b212f6480.png

按F7,来到其父类的构造函数

1759125827326-08df02c7-5f1e-420a-9ec4-be28b2884a83.png

这里的父类构造函数的port传入了一个0,它代表一个随机端口。

1759112906419-2976f169-2539-41e8-8835-6e7c5aacc82c.png

1759113067127-3a69b877-7ed4-45c6-9ccf-89a3e9aaba6f.png

这里的 exportObject(Remote obj, int port)是一个静态函数,它主要是负责将远程服务发布到网络上,它第一个参数this代表我们刚刚创建的RemoteObjlmpl实例,第二个参数是new UnicastServerRef(port)

  • UnicastServerRef是RMI服务端的核心引用实现。它负责管理网络端点、监听端口、以及处理远程方法调用。可以把它看作是服务对象和网络之间的“桥梁”或“适配器”。

继续跟进此函数,去到UnicastServerRef的构造函数。

1759148627681-0e37d3fd-168a-4840-82f4-139da5070b7e.png

发现这里new 了一个 LiveRef(port),它是一个网络引用的类

  • LiveRef:直译就是“活动引用”,它是一个非常底层的类,直接代表了一个远程对象在网络上的具体位置。它内部封装了两个关键信息
    • 对象标识符:一个唯一的long类型数字,用来在一台主机上区分不同的远程对象
    • 端点:代表网络地址和传输协议

1759149030618-bf3dfbec-d02b-48c9-bcf7-86b68128cb9a.png

1759150879773-7b16361f-65c3-4a1f-a4ae-95b9d6cb2377.png

查看跳进this后的构造函数,第一个参数为ID,第三个参数为true,所以我们重点关注第二个函数,也就是TCPEndpoint.getLocalEndpoint(var2)

  • TCPEndpoint:它是RMI中TCP/IP传输协议的具体实现。它封装了IP地址和端口号。当传入的port为0时,TCPEndpoint会记录下这个“动态端口”的意图。它内部会处理获取主机 的IP地址(getLocalHost

TCPEndpoint是一个网络请求的类,看一下它的构造函数,传参进去一个ip,一个端口,可以进行网络请求

1759151233535-ed4cc197-a1c0-4e69-9014-b26a10611843.png

继续跟进this

1759151474248-a350a737-9dea-42e1-9b9a-46e8e575b0df.png

这里看到所有信息都存到了LiveRef里面。


再回到super(new LiveRef(port));的地方

进入super看一下它的父类方法

1759316647742-c829c6bf-5702-4265-aa1d-24657622c98e.png

查看它的父类构造方法,这里只是进行了一个赋值,而不是建立了一个新的。

1759316672046-afdabfdd-03ca-4732-b9b5-b54c027bb7c7.png

一路f7到这里,这一部分代码是** UnicastServerRef 类的核心代码吗,完成了存根(stub)的创建,目标对象(Target)的封装以及最终的网络绑定。**

public Remote exportObject(Remote impl, Object data, boolean permanent) throws RemoteException {
    // 1. 获取实现类的 Class 对象
    Class<?> implClass = impl.getClass();
    Remote stub;

    // 2. 创建存根(Stub)
    try {
        stub = Util.createProxy(implClass, getClientRef(), forceStubUse);
    } catch (IllegalArgumentException e) {
        throw new ExportException(
            "remote object implements illegal remote interface", e);
    }

    // 3. 如果是旧的 Stub 机制,则设置骨架(Skeleton)
    if (stub instanceof RemoteStub) {
        setSkeleton(impl);
    }

    // 4. 创建 Target 对象,封装所有必要信息
    Target target = new Target(impl, this, stub, ref.getObjID(), permanent);

    // 5. 真正将服务绑定到网络上
    ref.exportObject(target);

    // 6. 缓存方法哈希映射,用于快速查找
    hashToMethod_Map = hashToMethod_Maps.get(implClass);

    // 7. 返回创建的存根
    return stub;
}

1759151744497-ff44d3c0-eb34-4240-a37a-2c1631f0ca8e.png

发现在这一步创建了stub

1759221296950-668c7df3-f623-49c6-8541-74385567f363.png

  • RMI先在服务端创建一个Stub,再把Stub传到注册中心,最后让客户端获取Stub

进入createProxy方法

1759317069841-9dcb7f88-45e5-46fc-8930-0e1aff688b06.png

这里的stubClassExits判断,如果有_Stub结尾的话,结果会返回为真。1759317218611-6650ad41-210e-4c81-b1dc-991f44eddef7.png

f8来到这里,这是一个创建动态代理的过程。

1759152110240-07bf56f7-072f-4f8c-845d-06313bb87703.png

可以看到这里有一个创建动态代理的过程,第一个参数是AppClassLoader,第二个参数是一个远程接口,第三个参数是调用处理器。

我们看一下调用处理器的创建过程

1759317356921-42719645-41a0-492e-ad69-315635b4769f.png

进入super方法之后看到这个

1759317298307-122db6af-e887-4450-80a5-759491753b9b.png

继续f8

如此如此,动态代理类就创建好了

1759152590584-4caa9d31-39f6-49c5-b0e6-eb2bfd34713b.png

我们再看Target类,这里的Target类相当于一个总的封装,将所有有用的东西都封装给了Target类

1759152874659-7815bfe6-d2ec-4c22-a98e-0d248a1ef4f0.png

1759152861383-4a56c6e0-5e5b-42aa-9c10-c3525d323db9.png

注意到这里的var2和var3也就是disp和stub,一个服务端,一个客户端,他们ref的id都是一样的,都是824。

一路f8,回到之前的Target,

1759153076229-447d30d6-dd7b-4971-b6be-162032873591.png

这一语句的作用就是把target这个封装好的对象发送出去,真正把服务绑定到网络上。

看一下它的发送逻辑,一路f7到这里

1759153171137-07adf11b-61fd-4622-8ebf-368c4ebf8992.png

从这里开始,第一句listen,真正处理网络请求了,跟进去

1759153229473-bb640b87-8f94-45ff-913b-e121fc1fa313.png

先获取TCPEndpoint,然后我们继续f8往后看,直到 this.server = var1.newServerSocket();,这是listen()方法的核心操作。

  • var1.newServerSocket() 的内部:这个方法会调用 new ServerSocket(port, backlog, bindAddr),在指定的 IP 地址和端口上创建一个套接字,使其进入 “监听” 状态。如果端口被占用,会抛出 BindException

1759153345647-1f66a899-d23a-4505-b5b3-5f44059e783d.png

它创建了一个新的进程,然后等待客户端连接。

1759317738881-be3f4f77-162f-420e-b2d3-95f57c6ac5fc.png

继续按F8出去代码逻辑来到这里,可以看见一开始 liveRef的默认端口是0,实际上这里已经随机分配了一个端口了

1759317973299-38742910-d4e2-4f65-b469-6825d6b24d69.png

这是因为在 this.server = var1.newServerSocket();语句的时候,就调用了下图的代码,可以看见如果端口为0,它会随机给它一个值,然后返回服务端。

1759318045750-e5ac6836-3ec3-4a70-80be-e55f45dff2b8.png

服务端记录发布

f8来到这里,然后按f7,进入expoortObject方法

1759318493881-468b7ef3-929c-486e-a423-a60a9d33cda0.png

这里调用了putTarget方法

1759318517526-880d86d9-618a-4712-9651-fec68a353b70.png

f7进入此方法,可以看见这两个方法

1759318624598-5ac55375-4e28-4957-82d3-8f1912e7c29c.png

这两个方法会把信息保存到这两个table

1759318694203-a38c9a30-3910-4357-a99c-f87aae7d4bf4.png

最后

// 6. 缓存方法哈希映射,用于快速查找
    hashToMethod_Map = hashToMethod_Maps.get(implClass);
小总结

总结**:执行**** RemoteObjlmpl remoteObjlmpl = new RemoteObjlmpl(); **,触发了一个复杂的、自动化的网络服务启动流程。这个流程可以概括为:

  • 触发导出UnicastRemoteObject 的构造函数调用 exportObject()
  • 封装网络地址:
    • UnicastServerRef 创建一个 LiveRef
    • LiveRef 创建一个 TCPEndpoint,用它来记录服务器的 IP 地址和要监听的端口(此时为动态端口)。
  • 启动网络服务:
    • TCPTransport 根据 TCPEndpoint 打开一个 ServerSocket,并绑定到一个由操作系统分配的具体端口上。
    • 一个后台 Acceptor 线程被启动,开始监听这个 ServerSocket 上的客户端连接。
  • 建立映射关系
    • RMI 内部维护一个表格,将接收到的网络请求(包含对象 ID)与我们的 remoteObjlmpl 实例关联起来。

最关键的还是UnicastServerRef** 类的此核心代码**

public Remote exportObject(Remote impl, Object data, boolean permanent) throws RemoteException {
    // 1. 获取实现类的 Class 对象
    Class<?> implClass = impl.getClass();
    Remote stub;

    // 2. 创建存根(Stub)
    try {
        stub = Util.createProxy(implClass, getClientRef(), forceStubUse);
    } catch (IllegalArgumentException e) {
        throw new ExportException(
            "remote object implements illegal remote interface", e);
    }

    // 3. 如果是旧的 Stub 机制,则设置骨架(Skeleton)
    if (stub instanceof RemoteStub) {
        setSkeleton(impl);
    }

    // 4. 创建 Target 对象,封装所有必要信息
    Target target = new Target(impl, this, stub, ref.getObjID(), permanent);

    // 5. 真正将服务绑定到网络上
    ref.exportObject(target);

    // 6. 缓存方法哈希映射,用于快速查找
    hashToMethod_Map = hashToMethod_Maps.get(implClass);

    // 7. 返回创建的存根
    return stub;
}

服务端创建注册中心

1759319166755-903348bb-c343-45ca-b483-d1b8a019e95a.png

创建RegistryImpl对象,可以看到创建注册中心的默认端口为1099

1759319342373-b337423c-1b82-484a-8c74-c5f42be32666.png

来到注册中心的实现类

1759319811885-56520528-28e0-4afa-843b-2a6f3f4f0559.png

f7进去setup方法

1759320080416-e40c2d83-e489-4ef9-8072-6adc58930fba.png

在 exportObject的方法可以看见参数 permanent的意思为永久,意思是我们创建注册中心这个对象为永久对象

exportObject又进入了熟悉的这个UnicastServerRef方法

1759320255813-c3fb6aa0-1da8-4320-9683-f54f61fbfd45.png

进入createProxy方法

1759320347438-e2076683-5273-4d59-b73a-c20b52fe2ce2.png

进入createStub方法

1759320371696-5a926e3c-0004-4b1e-a4a1-aa27d4290182.png

可以看见,类名的名字改变了,return 返回了加载的初始化 ref

返回UnicastServerRef,进入setSkeleton

1759320471327-8ee36cb4-6173-4773-9f86-9a5640896e8b.png

1759320609395-fcfad0b0-4026-49bd-a078-89f1925775b3.png

1759320632666-bab8a451-e344-42bb-a208-6323fd1bfb97.png

跟踪方法

1759320676012-a0ac1b05-9069-4790-927b-12a1a452cbff.png

来到这里,可以发现static中的数据的 objTarget的第二个Target对象的Value的值有一个 DGCImpl_Stub。它是分布式垃圾回收的一个对象,并不是我们创建的,而且这里有三个Target后面会说到。

1759321417118-93a1acc0-f0c1-49b8-a399-dfb07e5d988e.png

1759321579984-21ab7c1a-897a-42bc-9915-0b5fb3c10444.png

小总结

这部分代码创建了一个注册中心,它在1099端口监听,等待其他RMI服务(如我们的 RemoteObjlmpl来注册地址,也等待客户端来查询这些地址。

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

1759322102513-1d568cc5-f0e4-43a9-9af9-3cd304b5682e.png

来到这里

1759322125698-08225c4d-d413-4fb3-867b-f5aee6dccbb6.png

public void bind(String var1, Remote var2) throws RemoteException, AlreadyBoundException, AccessException {
    // 1. 安全访问检查
    checkAccess("Registry.bind");

    // 2. 同步代码块,保证线程安全
    synchronized(this.bindings) {
        // 3. 检查名称是否已被绑定
        Remote var4 = (Remote)this.bindings.get(var1);
        if (var4 != null) {
            // 4. 如果已绑定,则抛出异常
            throw new AlreadyBoundException(var1);
        } else {
            // 5. 如果未绑定,则将名称和对象存入映射表
            this.bindings.put(var1, var2);
        }
    }
}

实际上这个bindings就是Hastable表

1759322442797-705ad6f4-1712-4bff-9679-4b2aa635e588.png

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

在服务端主程序中进行打断点debug调试。

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

1759325413490-95129a83-8a30-42d9-ae81-d0ba16fb6fda.png

1759325375487-b2620140-6634-460c-8931-85a40ed2b67f.png

是注册中心的代理,所以走到这个方法里

1759325397152-58da31a4-5652-4ef1-9e87-738973743e67.png

1759325452827-db55add0-a737-4720-b52b-822cf71ec80a.png

1759325933029-ecf69aea-b162-4baf-8234-d0c4df05322f.png

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

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

在客户端打断点调试

1759326122288-84c5e7ac-4571-41ea-8157-6240b8be7cd0.png

1759326102177-f63805df-4d77-4c0b-a3b4-a6eb042e0cc4.png

来到

1759326446540-329b6ba5-baf0-4726-ab92-2b954fd3abd3.png

进入createProxy,这里返回创建好的Stub对象。

1759326626857-93078b33-43dc-4499-ba3c-113f493c8010.png

至此客户端获取注册中心代理对象就到这里了。

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

1759326789574-7f90707d-4495-4b42-a183-e36adb0e7b79.png

1759327394550-bd2a73a8-241c-49bf-94fc-26a8f3073c6b.png

1759327391400-de25a0ca-9c3f-4e24-bce1-62d13d8bae0f.png

来到executeCall方法

1759327471263-2c456d84-409f-408c-b820-48980e3a7734.png

此方法主要是处理网络请求,这个方法中也使用了反序列化方法,也就是说调用invoke,都有可能执行反序列化。

1759327725092-aa3aeef2-209d-465a-be00-e8e1574c6a85.png

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

这里需要服务端与客户端之间的交互,在服务端主程序进行DEBUG操作,然后断点如图。

1759328025335-bef9374c-cdc4-4cac-b676-e51b0be38f51.png

调试来到这里Transport#disp.disppath

1759328179893-775619fe-c7a0-4156-9135-beb3ae037263.png

这里的skel只有注册中心才有,当判断是注册中心就会调用oldDispath方法,显然这里满足条件

1759328304388-32a0aa73-2669-4b48-ae4f-7533c408d921.png

进行追踪调试,调用 skel.dispatch 方法

1759328350970-fc6831e2-41d2-453d-9856-5d70127e93c5.png

总的来说是 RegistryImpl_Skel类调用了dispath方法,然后lookup方法中有一个反序列化的点,这里是存在漏洞的

1759328445723-da17550e-f495-488c-98e6-bcbf1963a44f.png

最后服务端本地调用 RegiistryImpl.lookup(name),获取返回的远程对象,最后远程对象序列化,然后还给客户端,让它进行反序列化读取1759328458768-a396bb4c-d54c-4e24-b161-ad5f22bf13b6.png\ 至此,注册中心收到查询请求并返回远程对象代理,就到这了。

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

存在漏洞

1759329775100-3409be1b-c9ff-4b11-8659-aac568d960d8.png

因为客户端获取的是远程对象代理stub,也就是说它调用任意方法都会走到invoke里

1759330034199-5abec73c-3e25-4456-be91-44262198fac1.png

进入重载的invoke方法

1759330151534-66f0335e-1e66-484c-a48f-c578a4f24ab5.png

这里有一个marshalValue方法

1759330217236-f91230af-63c6-459f-91c6-008559a0e078.png

这个方法里进行了反序列化

1759330369631-cc522317-0cea-4c3c-b5bd-bb91dea29838.png

实际上call.exeuteCall()方法我们知道执行这个方法是存在漏洞的,客户端如果遇到了恶意的注册中心

1759330495494-fc383771-b43b-44ee-93cb-d895f18a74ba.png

跟进 unmarshalValue 方法,可以看见最后进行了反序列化的操作

1759330514323-4c46509c-c08c-49e4-83f5-307917d78746.png

可以看到之后返回了一个HELLO的值,成功反序列化的值

1759330539901-10d3d33a-a268-4fa0-8fe6-dadb17dc9eb5.png

到这一行,返回调用方法执行的结果,至此客户端调用对象方法结束

1759330546936-a77f0362-4bab-4480-8e24-f63525f1d293.png

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

服务端在这里打断点,然后开始debug调试

1759330622487-db8c2948-821e-41ac-bcdc-5361916e2b3c.png

这个时候在客户端运行主程序

然后f8来到这里

1759330680831-653ffc50-95df-4334-8c34-143638e8c1c7.png

按f9直到skel为null的时候f7调试

1759330713718-5744db10-1a47-4562-bb11-fe4e25dafd36.png

1759330719965-29e19a22-218d-4a4e-8951-74e9f9b8e8d3.png

继续往下走

1759330733030-2ab9ed1a-a01f-4352-a025-99b61b848d1e.png

主要有以下三个关键点

1759330754911-d1028960-9c99-4f4f-82cd-85c9c294cd9a.png

第一个关键点

先看第一个 unmarshalValue 方法,最后反序列化客户端序列化的内容

1759330789379-ce53d8f5-de25-40dc-9317-94138ba26837.png

因为要反序列化数据的类型是String,所以它绕过了前面的判断

1759330810968-7dee2159-b349-4d36-b1dc-d76d4cc801c4.png

可以看到反序列化参数成功了

1759330826440-c5a52f9f-9038-4c5c-beb3-db9787cfc417.png

第二个关键点

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

1759330879659-64ac267f-7c00-4b57-9c25-ba9f950729c6.png

第三个关键点

跟进到marshalValue方法,可以看见它是进行反序列化返回值的操作

1759330927068-dfbd1beb-dc70-419c-8074-103f70ac6d46.png

至此可以看见客户端进程直接运行完毕了,因为它收到了来自服务端发送的返回值

1759330976030-ef720b5e-4529-4084-9e51-09a2325e399f.png

服务端完成接受客户端的调用、执行本地函数、返回执行结果的过程就是这样

客户端请求服务端-dgc

DGC代理的产生

在这里下断点调试

1759331051668-66429977-ef9d-4aa0-b5ec-ab5583872bfe.png

这里发现stub是dgc代理

1759331073936-27fc1fab-cab6-4eac-9435-9721c9234768.png

来到DGClmpl的实现类进行断点调试

1759331115193-e00288c2-baaa-4584-ab24-0476c424f2c4.png

至此DGC_Stub的创建就完成了,DGC是一个自动创建的过程,用于清理内存。

DGC是实现类Stub

DGClmpl_Stub的类下有两个方法,一个是clean(强清除),一个是dirty(弱清除)

1759331244482-8a8b83bc-ab17-4d5d-9877-f5f23382fba6.png

在clean方法中存在反序列化的漏洞点

DGC实现类Skel

在DGClmpl_Skel类的dispash方法,存在反序列化漏洞的入口:

1759331309432-77952a86-5378-4d66-bfb0-f54a80b11372.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/
https://blog.csdn.net/weixin_53912233/article/details/139422625?fromshare=blogdetail&sharetype=blogdetail&sharerId=139422625&sharerefer=PC&sharesource=2301_80951345&sharefrom=from_link

这部分感觉好乱好杂,有点给我学死了。

更新: 2025-10-09 14:55:17
原文: https://www.yuque.com/cindahy/ukztx0/nv840v1693zhglg0

评论