RMI基础
一、RMI前言介绍
RMI作为后续漏洞中最为基础的利用手段之一,非常需要进行学习和深入理解。
需要注意的是:
- 影响版本:<=jdk8u121;
- 原因:>jdk8u121,bind、rebind、unbind三个方法只能对localhost进行攻击;
二、RMI基础
2.1 RMI介绍
RMI 全称 Remote Method Invocation(远程方法调用):在一个 JVM 中 Java 程序调用在另一个远程 JVM 中运行的 Java 程序,这个远程 JVM 既可以在同一台实体机上,也可以在不同的实体机上,两者之间通过网络进行通信。
RMI 依赖的通信协议为 JRMP(Java Remote Message Protocol,Java 远程消息交换协议),该协议为 Java 定制,要求服务端与客户端都为 Java 编写。
JRMP协议如HTTP协议一样,规定了客户端和服务端要满足的规范
- RMI包括以下三个部分
- Server服务端:服务通过绑定远程对象,这个对象可以封装很多网络操作,也就是Socket
- Client客户端:客户端调用服务端的方法
因为有了C/S的交互,而且Socket是对应端口的,这个端口是动态的,所以这里引进了第三个RMI的部分——Registry部分
- Registry注册端:提供服务注册与服务获取。即Server端向Registry注册服务,比如地址、端口等一些信息,Client端从Registry获取远程对象的一些信息,如地址、端口等,然后进行远程调用。
2.2 RMI的实现
这里为了便于理解,将服务端和客户端分为两个模块进行编写。
服务端RMI_Server
首先定义一个远程接口,其中定义一个sayHello()的方法
- RemoteObj.java(编写一个远程接口,其中定义了一个 sayHello() 的方法)
1 |
|
要求:
- 此远程接口要求作用域为public
- 继承Remote接口;
- 让其中的接口方法抛出异常;
- RemoteObjImpl.java(定义该接口的实现类 Impl)
1 |
|
要求:
- 实现远程接口
- 继承UnicastRemoteObject类,用于生成Stub(存根)和Skeleton(骨架)。
- 构造函数需要抛出一个RemoteException错误
- 实现类中的对象必须都可序列化,即都继承
java.io.Serializeable
- RMIServer.java(注册远程对象)
1 |
|
port默认是1099端口,不写会自动补上,其他端口必须写;
bind的绑定,只要和客户端去查找的registry一致即可
客户端RMI_Client
客户端需要从注册器中获取远程对象,然后调用方法。当客户端还需要一个远程对象的接口,不然不知道获取回来的对象是什么类型的。
- RemoteObj.java(编写一个接口,定义远程对象类型)
1 |
|
- RMIClient.java(编写客户端的代码,获取远程对象,并调用方法)
1 |
|
这样我们就能够从远端服务端中调用RemoteHelloWorld对象的sayHello()方法了。
三、从Wireshark抓包分析RMI通信原理
在这里直接说明RMI的通信原理,详细的分析流程可以参考从Wireshark抓包分析RMI通信原理
在客户端远程调用Java程序的过程中其实建立了两次TCP连接,第一次连接是连接1099端口;第二次连接是由服务端发送给客户端的。
- 第一次连接:客户端连接Registry在其中寻找Name为hello的对象,这个对应数据流中的Call消息,然后Registry返回一个序列化的数据,这个就是找到的
Name=Hello
的对象,这个对应数据流中的ReturnData消息。 - 第二次连接:服务端发送给客户端的Call消息客户端反序列化该对象,发现该对象是一个远程对象,地址在
ip:port
,于是再与这个地址建立TCP连接;在这个新的连接中,才执行真正的远程方法调用,也就是sayHello()
总的来说,RMI Registry就像一个网关,他自己是不会执行远程方法的,但RMI Server可以在上面注册一个Name到对象的绑定关系;RMI Client通过Name像RMI Registry查询,得到这个绑定关系,然后再连接RMI Server。最后,远程方法实际上在RMI Server上调用。
原理如下图所示:
至此,我们可以确定RMI是一个基于序列化的Java远程方法调用机制。
四、从IDEA断点分析RMI通信原理
4.1 流程分析总览
首先RMI有三部分:
- RMI Registry
- RMI Server
- RMI Client
如果两两通信就是 3+2+1 = 6 个交互流程,还有三个创建过程,一共是九个过程。
RMI 的工作原理可以大致参考这张图
4.2 创建远程服务
声明:创建远程服务其实并不存在任何漏洞
断点打在RMI_Server的创建远程对象上,如上图所示;
4.2.1 发布远程对象
开始调试,首先是远程对象的构造函数RemoteObjImpl
,现在我们要把它发布到网络上,分析的是它如何被发布到网络上去的
RemoteObjImpl
这个类是继承于UnicastRemoteObject
的,所以先会到父类的构造函数,父类的构造函数这里的port传入0,它代表了一个随机端口。
这个过程不同于注册中心的1099端口,这是远程服务的。
远程服务这里如果传入的是0,它会被发布到网络上的一个随机端口。继续往下看,先F8到exportObject()
,在F7步入进去查看。
1 |
|
exportObject()
是一个静态函数,它就是主要负责将远程服务发布到网络上
如何更好的理解exportObject()
的作用?
如果不继承UnicastRemoteObject
这个类的话,可以通过手动调用函数实现相对应的功能。For example:
1 |
|
再次回到exportObject()
这个静态函数,第一个参数是obj
对象,第二个参数是new UnicastServerRef(port)
,第二个参数是用来处理网络请求的。
继续向里跟进F7,来到UnicastServerRef
的构造函数。
在UnicastServerRef
的构造函数,我们看到它new了一个LiveRef(port),它算是一个网络引用的类,继续跟进查看。
先是一个构造函数,在继续跟进this查看
1 |
|
第一个参数ID
,第三个参数为true
,所以我们重点关注一下第二个参数。TCPEndpoint
是一个网络请求的类,我们可以去看一下它的构造函数,传参进去一个IP与一个端口,也就是说传进入一个IP和一个端口,就可以进行网络请求。
1 |
|
继续F7跟进LiveRef的构造函数this里面
这时候我们可以看一下一些赋值,发现host和port是赋值到了endpoint里面,而endpoint又是被封装在LiveRef里面的,所以记住数据实在LiveRef里面即可,并且这一LiveRef至始至终只存在一个。
上述即是LiveRef创建的过程,继续F7跟进会再回到之前出现LiveRef(port)
的地方
继续F7跟进super看一看父类UnicastRef
,这里就证明整个创建远程服务的过程只会存在一个LiveRef。一路F7到一个静态函数exportObject()
,后续的操作过程都与exportObject()
有关,基本都是再调用它,这一段不是很重要,一路F7就好。直到此处出现Stub
再服务端创建远程服务这一步居然出现了stub的创建(其实也就是一个代理对象),其实原理是这个样子的,来结合上述我们提到的原理图进行讲解:
- RMI现在Service的地方,也就是服务端创建一个Stub,再把Stub传到RMI Registry中,最后让RMI Client去获取Stub。
接着我们研究Stub产生的这一步,先进到createProxy这个方法里面
先进行了基本的复制,然后继续F8往下看,去到判断的地方。
这个判断暂时不用管,后续我们会碰到,那个时候再讲。
再往下走,我们可以看到这是很明显的类加载的地方。
第一个参数是AppClassLoader,第二个参数是一个远程接口,第三个参数是调用处理器,调用处理器里面只有一个ref,它也是和之前我们看到的ref是同一个,创建远程服务当中永远只有一个ref,那就是(LiveRef)。
在此处就把远程服务的动态代理创建好了,如图Stub。
继续F7跟到Target这里,Target这里相当于一个总的封装,将所有用的东西放到Target里面,我们可以进去看一看里面都放了什么。
通过比较ID可以知道在disp、stub中的ref是同一个,且ID值与存储在id中的值相同。
一路F8,回到之前的Target定义的位置,下一条语句是ref.exportObject(target)
,也就是把target这个封装好的对象发布出去。
F7跟进exportObject查看发布逻辑,跟进到如下图位置
此处的第一条语句listen(),真正处理网络请求,继续跟进查看逻辑。
跟进之后,可以知道其运行逻辑。
先获取了TCPEndpoint,然后到server = ep.newServerSocket();
创建了一个新的socket,等待连接,所以之后再Thread里面去做完成连接之后的事儿。
来到NewServerSocket()
函数中可以发现,当listenPort端口等于0的时候,会设置一个默认端口。
下面贴上线程中执行任务的图示
这里就是比较常见的Socket连接请求的处理流程,
一路执行回到exportObject()中可以发现,target里面的port变量已经被赋值。意味着刚才的NewServerSocet()
函数确实已经对port端口进行赋值。
4.2.2 发布完成之后的记录
其实经过上述的操作之后就已经完成了发布stub,继续向下查看一下记录;
从这里F7跟进内部查看远程服务被记录到什么地方。
第一个语句target.setExportedTransport(this);
是一个简单的赋值,可以不看;
第二个语句ObjectTable.putTarget(target);
继续跟入;前面都是一些简单的赋值语句,调用的put函数
RMI这里会把所有的信息保存到两个table里面,从而形成相应的表进行记录(其实就是一个Map),应该是可以简单理解为一个日志的。
4.2.3 创建远程服务小结
从个人理解的角度来说,发布远程对象,就是通过exportObject()
指定到发布的IP与端口,端口为随机端口。
至始至终复杂的地方其实都是再赋值,创建类、进行各种各样的封装,真正实现功能的代码还是比较短的,即listen()
;以及创建线程开始执行监听;
还有一个过程就是发布完成之后的记录,类似于日志,这些记录时保存到静态的HashMap当中。
创建远程服务这部分的内容基本就是这样,事实上并不存在漏洞;
4.3 创建注册中心+绑定
创建注册中心与服务端是独立的,所以谁先谁后其实没有什么关系,本质上都是一整个东西;断点打在Registry registry = LocateRegistry.createRegistry(1099);
4.3.1 创建注册中心
首先从断点createRegistry()
函数中通过F7跟进函数内部查看其详细内容。第一步是创建一个RegistryImpl
对象,继续跟进到RegistryImpl
构造当中。
在这个构造函数当中,首先会判断传入的端口port
是否已经是注册中心的端口以及进行一系列的安全检查判断(不重要)。继续F8往下走……
可以发现其创建了一个LiveRef对象,id参数值不明,但是port是我们的注册中心端口1099;并且创建了一个UnicastServerRef
对象,这段代码和我们么那上面讲的创建远程对象比较类似,可以继续跟进查看一下setup()
函数
发现其实跟创建远程对象的原理相似,先赋值,然后再进行exportObject()
方法的调用。
区别在于
第三个参数的不同
创建远程对象中为true
,代表创建一个临时对象;
创建注册中心中为false
,代表创建注册一个永久对象;
然后再通过F7跟进exportObject()
函数查看一下函数的实现细节;
发现其实跟发布远程对象中的exportObject()
差不多,来到创建Stub的阶段;继续跟进Stub的详细创建的过程中createProxy()
;
Stub创建过程中的判断与创建远程对象服务中存在差异,就是就在于stubClassExists()
判断的存在,继续跟进该判断函数中
我们看到这个地方,是判断是否能获取到 RegistryImpl_Stub
这个类,换句话说,也就是若 RegistryImpl_Stub
这个类存在,则返回 True,反之 False。我们可以找到 RegistryImpl_Stub
这个类是存在的。
对比发布远程对象那个步骤,创建注册中心是走进到 createStub(remoteClass, clientRef);
进去的,而发布远程对象则是直接创建动态代理的。继续F7跟入createStub()
中
直接通过反射调用构造函数创建对象,里面放置的内容即ref。一路F8执行代码来到
如果服务端是定义好的,就调用setSkeleton()
方法,跟进去
存在一个createSkeleton()
方法,从函数名可以看出是用来创建Skeleton
的。在RMI原理流程图中,Skeleton是作为服务端的代理使用的。
查看一下createSkeleton()
函数源码,发现Skeleton
是用forName()
的方式创建的,如图。继续F8执行,来到下图
发现此处比发布远程服务对象多了skel
,又到了Target的位置,Target部分的作用与发布远程服务的作用一致,用于存储封装数据;接下来的exportObject()
的作用基本与前面分析的一致。listen()
线程监听等待接收socket
连接。F7跟进后,再次来到这个位置即可
继续F8到super.exportObject(target);
然后F7跟进查看相关内容即可;
这里存在一个putTarget()
方法,它会将target的数据封装并放进去。继续F7跟进查看;
执行的代码基本上与创建远程服务一致,但是还需要详细查看一下封装了哪些数据。
4.3.2 查看封装了哪些数据进去
查看 static 中的数据,点开 objTable
中查看三个 Target,我们逐个分析一下,分析的话主要还是看 ref
我们现在可以关注到这里总共有三个Target,我们逐一分析其中存储的内容;
- Target@844
可以知道这是DGCImpl_Stub,是分布式垃圾回收的一个对象;它并不是我们刚才创建的。这个东西挺重要的。
- Target@789
可以发现这里存储的是$proxy
对象,具体信息如上图所示;
- Target@880
可以发现这个target中应该是存储了我们创建的服务中心信息,包括监听端口等等;
4.3.3 绑定
现在来到了RMI服务端的最后一步,也就是bind绑定操作;
下断点在绑定语句进行调试分析;
这里可能不能直接通过F7跟进,可以找到RegistryImpl#bind()
下一个断点进来,通过F7跟进,来到bind()
方法的具体实现
继续F7根据到检查函数checkAccess()
的具体实现
F8执行跳出该函数,回到bind()
函数中
此处开始检查bindings 是否已经存在了相应名称的绑定,如果bindings中已经存在相应名称的绑定就抛出异常。其实 bindings 就是一个 HashTable。
继续F8执行,来到bindings.put(name, obj);
中,
其实就是将服务端代理放进去(这里的调试经过了两次启动,与上面分析的代理端口不一致),到这里,绑定的过程就结束了,其实就是把上面创建的代理和相应的名称放入bindings这个hashtable中而已;
4.3.4 创建注册中心和绑定小结
- 注册中心的创建过程和发布远程对象比较相似,只不是注册中心是创建一个持久对象,该持久对象就是注册中心;
- 绑定过程比较容易理解,就是将创建的代理和相应的名称进行绑定;代理中包含了相应的ip地址和端口;
4.4 客户端请求,客户端调用注册中心
4.4.1 获取注册中心
这里不存在任意漏洞,为了深入理解RMI,还是进行调试一下;
对客户端连接请求下断点进行调试;
直接F7跟进getRegistry()
方法中,查看具体的实现方法;
来到此处后,我们继续F8执行,来到LiveRef
定义与赋值这里;
又看到了比较熟悉的流程,创建liveRef,再创建ref;其实本质上与注册中心和远程服务类似;
这里创建的是我们请求连接的ip地址和port端口的liveRef和ref,然后再creatProxy()
;
通过提供的IP地址和端口就获取到了注册中心的Stub,然后继续进行下一步的查找远程对象;
4.4.2 查找远程对象
存在漏洞
由于之前返回的对象是RegistryImpl_Stub
类型,调用的对象应该在RegistryImpl_Stub
类中的lookup方法
因为对应的 Java 编译过的 class 文件是 1.1 的版本,无法进行打断点,所以会直接跳到其他地方去,比如此处。直接硬分析!
首先就是我们调用lookup的时候传入了一个String类型的参数remoteObj
,对应的就是方法中的形参var1
;
可以知道我们传入的数据经过了序列化在进行传入。后续注册中心也会经过反序列化读出数据;
接着下一步,我们看到 super.ref.invoke(var2);
,super 就是父类,也就是我们之前说的 UnicastRef
这个类。这里的 invoke()
方法是类似于激活的方法,invoke()
方法里面会调用 call.executeCall()
,它是真正处理网络请求的方法,也就是客户端的网络请求都是通过这个方法实现的。
这个方法后续再细讲,先看整个代码运行的逻辑。
我们的逻辑现在是从 invoke()
—> call.executeCall()
—> out.getDGCAckHandler()
,到 out.getDGCAckHandler()
这个地方的时候,是 try 打头的,这里它有一个异常存在潜在攻击的可能性,如图,中间省略了部分代码。
分析in
变量的赋值;
不难理解,in 就是数据流里面的东西。这里获取异常的本意应该是在报错的时候把一整个信息都拿出来,这样会更清晰一点,但是这里就出问题了(如果一个注册中心返回一个恶意的对象,客户端进行反序列化,这就会导致漏洞。)这里的漏洞相比于其他漏洞更为隐蔽。
也就是说,只要调用
invoke()
,就会导致漏洞。RMI 在设计之初就并未考虑到这个问题,导致客户端都是易受攻击的。上述就是注册中心与客户端进行交互时会产生的攻击。
我们这里继续 f8,看一下到最后一步的时候获取到了什么数据。简单来说就是获取到了 RemoteObj 这个动态代理,其中包含一个 ref。
4.4.3 总结
- 获取注册中心
在该部分内容中确实不存在漏洞,过程也相对简单,直接通过传入的ip和port创建Stub即可;
- 查找远程对象
该部分确实存在漏洞攻击的可能性,在处理网络请求的过程中,若存在异常,则将信息全部取出并进行反序列化,从而导致漏洞;
这是服务端攻击客户端的可能性,但是异常信息取出进行反序列化实现攻击,似乎没有那么简单。(这也恰恰说明,我的功夫不到家啊~~~)
据说,这里可以利用 URLClassLoader 来打,我继续学学吧!
4.5 客户端请求,客户端请求服务端
4.5.1 分析查询远程对象
这部分内容存在漏洞,重点关注分析
这里就是客户端请求的第三句代码——remoteObj.sayHello("hello");
这里如果 Debug 有问题的话,可以先在 RemoteObjectInvocationHandler
类下的 invoke()
方法的 if 判断里面打个断点,这样才能走进去。调试开始
报错:跳过的断点在java. rmi. server. RemoteObjectInvocationHandler:165, 因为它发生在调试器评估中 排除故障指南
算了,这里我硬看了!不调试了
首先经过一串的if判断,都是关于抛出异常的,直接跳过,不影响最后的理解。知道尾部的invokeRemoteMethod(proxy, method, args);
,跟进;
来到该函数中在继续查看相应的内容,继续跟进,由于ref是UnicastRef
,因此我们需要跟进UnicastRef#invoke()
;
1 |
|
这方法真长啊!这是一个重载的方法,这个重载的 invoke
方法作用是创建了一个连接,和之前也比较类似。我们可以看一下它具体的逻辑实现。
在该方法中存在一个 marshalValue()
方法。
它会序列化一个值,这个值其实就是我们传进的参数 hello
,它的逻辑如图。判断一堆类型,之后再进行序列化。
继续往后看刚才的invoke()
方法我们看到一个注释 // unmarshal return
,后面接的是 call.executeCall()
,之前我们也看到了这个方法,也就是说只要 RMI 处理网络请求,就一定会执行到这个方法,这里是存在危险的,原理上面已经代码跟过一遍了 ~
继续往后看!
这里有一个 unmarshalValueSee
的方法,因为现在我们传进去的类型是 String,不符合上面的一系列类型,这里会进行反序列化的操作,把这个数据读回来,这里是存在入口类的攻击点的。
这里的in数据其实就是执行完毕远程函数后的输出结果;
4.5.2 总结
- 在注册中心 –> 服务端这里,查找远程对象的时候是存在攻击的。
具体表现形式是服务端打客户端,入口类在 call.executeCall()
,里面抛出异常的时候会进行反序列化。
这里可以利用 URLClassLoader 来打,具体的攻击在后续文章会写。
在服务端 —> 客户端这里,也是存在攻击的,一共是两个点:一个是 call.executeCall()
,另一个点是 unmarshalValueSee
这里。
unmarshalValueSee
这里会将执行的输出返回到客户端,然后客户端进行反序列化从而实现攻击;
- 再总结一下代码的流程
分为三步走,先获取注册中心,再查找远程对象,查找远程对象这里获取到了一个 ref,最后客户端发出请求,与服务端建立连接,进行通信。
4.6 客户端发起请求,注册中心如何处理
4.6.1 注册中心处理分析
先说说断点怎么打,因为客户端那里,我们操作的是 Stub,服务端这边操作的是 Skel。在有了 Skel 之后应当是存在 Target 里面的,所以我们的断点打到处理 Target 的地方。断点位置如图
先点 Server 的 Debug,再跑 Client 就可以了,成功的打断点如上图;F8往下执行,查看target中包含了什么内容;
可以发现target中包含了一个stub,stub中有一个ref,对应的IP和port为注册中心监听端口;继续向下执行;
final Dispatcher disp = target.getDispatcher();
将target中的disp取出放到disp中,其中disp中包含了skel
和ref
等内容;
继续往下走,它会调用 disp 的 dispatch 方法,我们跳进去看一下 disp.dispatch()
继续走,我们目前的 skel
不为 null,会到 oldDispatch()
这里,跟进。
继续执行到410行的skel.dispatch(obj, call, op, hash);
,这里无法继续跟进了,因为具体的方法实现在RegistryImpl_Skel#dispatch()
无法下断点跟进;
下面就是 skel.dispatch()
的过程了,这里才是重点,这里就是很多师傅文章里面会提到的 客户端打注册中心 的攻击方式。
1 |
|
上述的方法的实现源码,分析一下源码;
我们与注册中心进行交互可以使用如下几种方式:
- list
- bind
- rebind
- unbind
- lookup
这几种方法位于 RegistryImpl_Skel#dispatch
中,也就是我们现在 dispatch 这个方法的地方。
如果存在对传入的对象调用 readObject
方法,则可以利用,dispatch
里面对应关系如下:
- 0->bind
- 1->list
- 2->lookup
- 3->rebind
- 4->unbind
只要中间是有反序列化就是可以攻击的,而且我们是从客户端打到注册中心,这其实是黑客们最喜欢的攻击方式。我们来看一看谁可以攻击。
- bind——可以进行反序列化攻击
- lookup——可以进行反序列化攻击
- rebind——可以进行反序列化攻击
- unbind——可以进行反序列化攻击
4.6.2 总结
简单,注册中心就是处理 Target,进行 Skel 的生成与处理。
漏洞点是在 dispatch 这里,存在反序列化的入口类(除了list,其他都可以攻击)。这里可以结合 CC 链子打的。
4.7 客户端发起请求,服务端做了什么
与调试注册中心处理时一致,将断点打在如图的位置;
首先运行Server的debug,然后再运行Client即可,可以看到程序成功运行到断点处;
调试这里有一点小坑,打完两个断点之后,我们得到的第一个 Target 中的 Stub 是 RegistryImpl 的,我们要的不是这个,我们需要的服务端动态代理的Stub,即Proxy
4.7.1 服务端处理分析
继续F9执行程序,再查看target中的stub,知道得到Proxy的Stub为止,如下图;
我们继续F8执行到dispatch()
方法处,跟进
继续执行程序来到判断结构可以知道,无论是num值还是skel,都表示我们不会直接执行oldDispatch()
函数,如下图所示
继续F8执行程序可以看到下面就是到了获取输入流,以及Method,Method其实就是我们在客户端调用的sayHello()
方法
继续执行就会来到循环当中的unmarshalValue()
方法,之前也提到过很多次这个方法,
其中存在漏洞,会将我们传入的参数hello
进行反序列化读出
4.7.2 总结
这里的服务端处理客户端的请求连接的过程中存在漏洞,会讲客户端发送过来的参数进行反序列化读取出来;
4.8 DGC
说实在的,个人认为这部分内容还是相当重要的,因为设置到后面的jrmp绕过相关知识,后面涉及的利用的还是蛮多的。
4.8.1 创建DGC
首先我们需要了解一下DGC的创建流程到底在哪里,如果我们对前面的调试流程的记忆比较清晰的大概可以记得,我们在ObjectTable中存在一个不明的判断。其实DGC就是在这个时候创建的。
实际上我们发现它这是调用了DGCImpl的静态变量dgcLog,在这个过程中会对类进行初始化,调用其中静态代码块中的内容然后执行,所以我们跟进DGCImpl类中的静态代码块。
可以看到在DGCImpl
类中存在一个静态代码块,在我们调用DGCImpl
的静态变量dgcLog的时候就会自动调用该静态代码快进行创建DGC,同时也创建了DGCImpl_skel,DGCImpl_Stub对象,以及封装Target对象,过程与之前的客户端与服务端分析的类似,这里就不在细说了。
4.8.2 DGC漏洞点
在了解了DGC漏洞点的基础之后,我们来看看DGC到底存在哪些问题?
首先,在DGCImpl_Stub
类中存在两个方法,一个是 clean,另外一个是 dirty。
clean 就是”强”清除内存,dirty 就是”弱”清除内存。
来查看类的具体实现我们可以发现,其实在dirty()
方法中存在readObject()
方法的调用,存在反序列化的入口类,可以尝试去进行反序列化漏洞的利用。
相同的,我们来到DGCImpl_Skel
类也可以发现,其中存在一个方法dispatch()
,其中也调用了readObject()
函数,存在反序列化的入口类,可以尝试去进行反序列化漏洞的利用。
这是在DGC我们需要关注的点,DGC这一块的具体利用可以说到jrmp绕过和JEP290等等,后续在开新章节详细说明;
五、总结
- RegistryImpl_Stub和DGCImpl_Stub中远程方法(lookup/list/dirty)的返回值
- 客户端调用UnicastRef.unmarshalValue反序列化读取远程方法的返回值
- 客户端反序列化读取远程方法执行时出错抛出的异常类
参考连接: