xiao1star2025-12-26文章来源:SecHub网络安全社区
Java RMI 是一种允许一个 Java 虚拟机(JVM)中的对象调用另一个 JVM 中对象的方法的技术。它是一种基于 Java 的分布式计算技术,使得开发者可以轻松地实现跨网络的远程方法调用。RMI分为一个Server端和一个Client端,其实还有一个注册中心,一般情况注册中心的创建都是在Server端

**Client端:**客户端通过注册中心查找远程对象的引用(Stub)。客户端需要知道注册中心的地址和远程对象的名称。
**Server端:**服务器端将远程对象实例注册到注册中心,使得客户端可以通过注册中心找到并调用这些远程对象的方法;服务器端负责处理客户端的远程方法调用请求,并返回结果
**注册中心:**注册中心是一个特殊的 RMI 服务,用于存储远程对象的引用(Stub)。服务器端将远程对象注册到注册中心,注册中心会为每个远程对象分配一个唯一的名称。
三者之间的关系:注册中心是Client端与Server端的桥梁、Server端提供服务、Client端调用服务
在正常的RMI机制中,是由两台不同的计算机实现的,但是资源有限这里是在一台主机上创建了两个项目分别是RMIServer和RMIClient
在RMIServer项目中有
远程接口:继承Remote类、声明要调用的方法
远程接口实现类:继承UnicastRemoteObject类以及远程接口、实现远程接口中要调用的方法
RMI服务类:创建注册中心、实现远程对象的绑定
import java.rmi.Remote;
import java.rmi.RemoteException;
/*
* 远程接口,需要继承 java.rmi.Remote接口,并在接口中声明要暴露给远程调用的方法
*
* */
public interface RemoteItf extends Remote {
String Hello(String name) throws RemoteException;
}
**注意:**这个Hello方法必须中的throws RemoteException不能省略,因为 Java RMI 中,远程接口中的方法必须声明抛出 RemoteException,这是由 RMI 的设计和工作原理决定的,若省略会有报错
import java.rmi.RemoteException;
import java.rmi.server.UnicastRemoteObject;
/*
* 实现远程接口的类需要继承自 java.rmi.server.UnicastRemoteObject
* 并继承远程接口类实现接口中定义的方法。
*
* */
public class RemoteImpl extends UnicastRemoteObject implements RemoteItf {
public RemoteImpl() throws RemoteException{
}
@Override
public String Hello(String name){
String upperCase = name.toUpperCase();
System.out.println(upperCase);
return upperCase;
}
}
import java.rmi.RemoteException;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
/*
* 通过java.rmi.registry.LocateRegistry 将远程对象绑定到 RMI 注册表中。
* */
public class RMIServer {
public static void main(String[] args) throws RemoteException {
RemoteItf remoteItf = new RemoteImpl();
Registry registry = LocateRegistry.createRegistry(1099);//创建注册中心、端口是1099
registry.bind("remoteItf", remoteItf);//远程对象的绑定
}
}
在RMIClient项目中有
远程接口:与RMIServer的远程调用接口一模一样,为了防止获取到远程对象之后没有这个类而报错
RMI客户端类:用于获取注册表中的远程对象并调用远程对象
import java.rmi.Remote;
import java.rmi.RemoteException;
public interface RemoteItf extends Remote {
String Hello(String name) throws RemoteException;
}
import java.rmi.NotBoundException;
import java.rmi.RemoteException;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
/*
* 通过 RMI 注册表查找远程对象的引用.并通过代理对象调用远程方法。
* */
public class RMIClient {
public static void main(String[] args) throws RemoteException, NotBoundException {
Registry registry = LocateRegistry.getRegistry("127.0.0.1",1099);//获取注册中心
RemoteItf remoteItf= (RemoteItf) registry.lookup("remoteItf");//通过注册中心查找远程对象
String rmi = remoteItf.Hello("rmi");
System.out.println(rmi);
}
}
首先启动服务端的项目,实现注册中心的创建以及远程对象的绑定

之后启动客户端的项目,实现从远程对象的获取以及方法的调用

之后再服务器端也成功的回显了RMI字符串


还是先看这个图片:
RMI中的注册中心(Locate Registry)是一个hash表,里面是名字以及其对应的远程对象,在图中可以看出客户端不是直接调用服务端的,而是通过代理,其中客户端的代理是Stub、服务端的代理是Skeleton。有这些代理的原因是因为像服务端的内容是不会想要实现网络请求中的东西,而这两个代理就是将网络请求的内容封装起来
还可以看到整个RMI的通信是由6条:
服务端–>注册中心;注册中心–>服务端
客户端–>注册中心;注册中心–>客户端
客户端和服务端通过JRMP协议相互通信
我们的分析这个目的就看看如何将一个远程对象发布到网上去
在RemoteItf remoteItf = new RemoteImpl();打上断点

下一步来到RemoteImpl类中,调用其类中的构造方法,要注意该该类中继承了UnicastRemoteObject类

接着就来到了UnicastRemoteObject类的构造方法,注意这里我也是打了一个断点,因为我这里不打断点根本进不来,发现这个方法直接又调用了其类中的一个有参的构造方法其中的port参数传入的值为0

接着就来到UnicostRemoteObject类中的一个有参的构造方法,再往后的操作就是将远程对象发布到一个随机的默认端口,其实在RMI中的默认端口是注册中心的端口,但是我们现在分析的是远程对象并非注册中心的,这是给的一个随机的端口

来到UnicostRemoteObject中的exportObject方法中,这个方法似乎是一个导出方法,也是一个静态方法,这个exportObject方法前一个参数是我们的远程对象,后一个参数是调用了创建了一个UnicastServerRef对象,而这个UnicastServerRef类看名字就是一个服务端引用类,专业点说就是专门用于在远程对象与客户端之间进行通信时提供服务器端的引用

若我们的远程对象没有继承UnicostRemoteObject类,那就需要我们手动调用这个静态方法

在往下就来就看看这个UnicastServerRef类的构造方法,听名字就是像一个服务端引用,这个构造方法又调用了其父类的构造方法,传入了一个LiveRef对象

接着看LiveRef类的构造方法,又是一个构造方法套构造方法,再第二个构造方法中我们发现又套了一个构造方法,其中第二个参数还调用了TCPEndpoint的getLocalEndPoint方法

接着就来到TCPEndpint类中,我们查看他的构造方法,发现是获取了host和port,可以想到这也是一个处理网络请求的封装类

获取到网络请求的相关内容后就将其结果赋值给ep,将其都封装在LiveRef类中

接着就来到TCPEndpint的getLocalEnpoint方法,将默认值port传入其中getLocalEnpoint中

接着来到TCPEndpoint的getLocalEndpoint方法中,该方法比较长
public static TCPEndpoint getLocalEndpoint(int port,
RMIClientSocketFactory csf,
RMIServerSocketFactory ssf)
{
/*
* Find mapping for an endpoint key to the list of local unique
* endpoints for this client/server socket factory pair (perhaps
* null) for the specific port.
*/
TCPEndpoint ep = null;
synchronized (localEndpoints) {
TCPEndpoint endpointKey = new TCPEndpoint(null, port, csf, ssf);//创建了一个新的TCPEndpoint对象
LinkedList<TCPEndpoint> epList = localEndpoints.get(endpointKey);//查看localEndpoints中是否存在键为endpointKey的键值对
String localHost = resampleLocalHost();//获取本地localhost
if (epList == null) {//这肯定是为空的,因为我们就没有对epList进行任何操作
/*
* Create new endpoint list.
*/
ep = new TCPEndpoint(localHost, port, csf, ssf);//创建了一个新的TCPEndpoint对象赋值给ep
epList = new LinkedList<TCPEndpoint>();
epList.add(ep);//将得到ep赋值给epList列表中
这是此时ep中的信息

将ep添加到epList中后eplist中的信息

ep.listenPort = port;
ep.transport = new TCPTransport(epList);//又将eplist中的内容作为参数创建一个TCPtTransport对象赋值给ep的transport成员变量
localEndpoints.put(endpointKey, epList);
if (TCPTransport.tcpLog.isLoggable(Log.BRIEF)) {
TCPTransport.tcpLog.log(Log.BRIEF,
"created local endpoint for socket factory " + ssf +
" on port " + port);
}
} else {
synchronized (epList) {
ep = epList.getLast();
String lastHost = ep.host;
int lastPort = ep.port;
TCPTransport lastTransport = ep.transport;
// assert (localHost == null ^ lastHost != null)
if (localHost != null && !localHost.equals(lastHost)) {
/*
* Hostname has been updated; add updated endpoint
* to list.
*/
if (lastPort != 0) {
/*
* Remove outdated endpoints only if the
* port has already been set on those endpoints.
*/
epList.clear();
}
ep = new TCPEndpoint(localHost, lastPort, csf, ssf);
ep.listenPort = port;
ep.transport = lastTransport;
epList.add(ep);
}
}
}
}
return ep;
}
ep的transport值中的内容

反正这个方法就是将host和prot赋值给ep然后将ep返回
接着就来到LiveRef的构造方法,将objID以及前面的ep和isLocal进行赋值操作

可以看到这是将前面获取的Endpoint对象封装到了LiveRef当中,也就是LiveRef@616

接着来到还是回到UnicastServerRef的构造方法,这又调用了其父类的构造方法UnicastRef

可以看到其父类是UnicastRef这是客户端的引用,大家可能会疑惑为什么客户端的引用要放在服务端创建呢,后面会有解释

可以看到这个liveRef就是我们之前获取到的Endpoint对象中的内容,将其又封装到了LiveRef中的LiveRef@616

上述内容就是创建一个UnicastServerRef对象,接下了我们查看UnicastRemoteObject类中的exportObject方法

接下来来到了UnicastRemoteObject的exportObject方法中,该方法看名字类似一个导出类,可以发现该方法首先是判断传入的第一个参数是否是UnicastRemoteObject子类,最终是调用sref.exportObject方法

要注意RemoteImpl继承的就是UnicastRemoteObject类,所以需要进入到if语句中,若不继承还是想要实现RMI服务端,就需要直接调用UnicastRemoteObject的exportObject方法

接着就来到UnicastServerRef的exportObject方法中该方法调用了Util的createProxy方法用于实现动态代理,将结果赋值给stub对象,接着创建了Target对象,然后调用LiveRef的exportObject方法,最终返回stub的结果

思考:为什么客户端的代理stub要在服务端创建呢
回答:这是这个stub需要在服务端创建好之后,接着放到注册中心上,然后客户端去注册中心上获取,进而与服务端的代理skeleton连接,进而获取到服务端的远程对象
先来到Util的creatProxy方法中,这是实现了一个动态代理

里面的handler参数中的clientRef就是我们之前得到的LiveRef@616

如下,这是一个if判断

这个stubClassExits用于判断是否有与remoteClass参数的值与_Stub而成的类名,要直接到这个remoteClass的值是实现远程接口类RemoteImpl,这是我们自己创建的一个类,肯定是没有的,结果为false;forceStubUse也为false,ignoreStubClasses的结果也为false。最终结果不会走到if语句中

只要调用的是如下类似于这种函数stubClassExits的结果才能返回为true

最后成功创建了一个动态代理将其赋值给了stub,如下可以看到stub所包含的内容,还是将LiveRef@616封装在了里面

接着来到Target的对象创建的分析,我们可以理解为将我们之前所创建的东西又封装到了Target,如下图所示可以看出disp(UnicastServerRef)和stub(UnicastRef)分别代表服务端和客户端,但是里面的内容都是一致的,都是LiveRef@616

创建完Target对象后,又调用了ref的exportObject方法将target发布了出去

来到LiveRef的exportObject中发现其又调用了ep.exportObject方法


逐步追踪,最后来到了TCPTransport方法中,先是会调用listen方法,之后又调用了其父类的exportObject方法

首先看listen方法,主要作用是监听一个TCP端口,以便接受客户端的连接请求。

来到newServerSocket方法中,该方法通常用于RMI服务端的实现,用于创建一个监听特定端口的服务器套接字。在该方法中若listenport的值为0,那么就会调用setDefualtPort方法随机生成一个端口

走完listen方法,服务端就已经把远程对象发布出去了,且发布在了一个随机的端口上,用户是不知道的
分析完listen方法我们来来到其父类的exportObject方法中,这个方法主要用于记录之前的内容


分析到这里整个Server端远程对象创建的过程就分析完了
在如下图所示LocateRegistry.createRegistry(1099);处,打上断点。这里传了一个port参数值为1099,用于后续注册中心开放的端口

来到LocateRegistry类下的createRegistry方法中,在这里面新建了一个RegistryImpl对象并将值为1099的port参数传入其中

来到RegistryImpl方法中,该方法首先是进行一个if判断,这是用于安全检验,不会进入到if语句中,这里就不多说了,会直接来到else语句中

先看new LiveRef(id, port),是不是感到十分熟悉,这在前面Server端远程对象创建中提到过,这里就不多说了

最终创建的LiveRef对象lref如下图所示

接着来到setup(new UnicastServerRef(lref));中的new UnicastServerRef(lref)中,这也十分熟悉

如下图所示是ref和liveRef的值都是LiveRef@889

接着来到RegistryImpl的setup方法中,接着调用了UnicastServerRef的exportObject方法,在第一个参数是RegistryIpml,第二个参数是null,三个参数是true

接着来到exportObject方法中,这也十分熟悉,之前分析过,这里就不多说了,但是与之前唯一的区别就是第三个参数parmanet的值是true, 表示这个代理的创建是永久的,我们直接看createProxy这个创建动态代理方法

来到createProxy方法中,这里与之前的远程对象的创建不同,远程对象创建是没有进入到if语句中,而是通过利用newProxyInstance来实现动态代理,而在这里直接进入了if语句中调用了createStub方法

为什么会进入到if语句中呢,只是因为我们要代理的接口是java中自带的类RegistryImpl,其存在后接stub的类名,所以进入了if语句中

来到createStub方法中

最终的到的stub的值是一个RegistryImpl_Stub对象

这个RegistryImpl_Stub类继承了RemoteStub类

因为上图的解释,所以调用了setSkeleton

接着来到setSkeleton方法中,在这个方法里面调用了createSkeleton方法

这是获取RegistryImpl_Skel的class对象利用反射来创建skeleton代理对象

接着就来到后面,首先是将前面的创建的东西封装到target中,之后再调用exportObkect方法

target中的内容

接着来到exprot方法中,和前面一样,从LIveRef–>TCPEndpoint–>TCPTransport

在TCPTransport的listen()方法中也是与之间分析的一模一样的,例如开启线程等操作,不做分析了,后面就会调用其父类的exportObject方法

最后来到Transprot的exportObject方法中,这就是将最后的target对象,放到一个Object表中,记录下来

可以看到ObjectTable的大小为三,按道理来说应该是两个,一个是Server远程对象创建时生成也就是,一个是创建注册中心生成的。我们点进去仔细看一下

如下是第一个的内容,可以发现第一个的skel是由DGCImpl_Skel创建的,而stub是由DGCImpl_stub创建的,这两个类我们在向前分析都没有看到,其实这是一个分布式垃圾回收,是系统默认会创建的,这个也是十分重要的,我们后面再分析

如下是第二个,可以看到这里的skel是为空的,而stub是通过RemoteObjectInvocationhandler类创建的,就是动态代理生成的,可以证明这个是前面Server端远程对象创建时生成的

如下是第三个,在这里可以看到skel是由Registry_Skel创建的,而stub是由Registry_Stub创建而成的,这也就是创建注册中心时生成的

到这里就把注册中心的创建分析完了
我们接下来分析registry.bind("remoteItf", remoteItf),这就是将所创建的远程对象与其对应的名字remoteItf进行绑定放入到一个Hashtable


若bings中不存在我们传入的值,那么就将其put到里面

可以看到成功put到了里面

总结
到这里Server端的创建分析全部完成,到目前为止我们都没有发现可以由漏洞利用的点,但是依旧进行大篇幅的分析是为了更好的了解Server端的运作机制
客户端的操作主要有两个操作:
1.向注册中心获取代理Stub
2.利用代理Stub向服务端做真正的远程调用
首先我们先分析LocateRegistry.getRegistry("127.0.0.1",1099),在这里我们给上了本地地址以及端口1099,用于去寻找Server端的注册中心,注意要运行Server端的代码
如下图打上断点

接着来到了LocateRegistry 的getRegistry

可以看到上面的是将是将host和port传递过去之后,创建了一个Registry对象,后面进行了对port和host进行了一系列的判断,也不会进入到if语句中
下面就来到了TCPEndpoint、LiveRef、以及UnicastRef,这些类我们前面都见过了,就是进行一些封装操作。之后调用Util.createProxy方法实现,这个和在服务端注册中心的创建的代码是一样的,传入的参数都是RegistryImpl的class对象

来到createPrxoy中,这里接着会来到createStub方法,创建一个stub代理

可以看到和向前的分析是一样的,也是利用反射来创建的

最终走完所有的,得到的结果如下图所示

总结:本以为Client端是通过注册中序序列化来远程获取服务端所创建的Stub,现在可以看到Client端是通过所给的参数port和host又在本地创建了一次Stub
接下来分析(RemoteItf) registry.lookup("remoteItf"),这是从注册中心获取到远程对象。我们知道实际上要调用RegistryImpl_Stub的lookup方法,但是这个用于本人的java版本问题导致只能看其.class文件,无法直接调试进入,我们就直接分析了

直接来到RegistryImpl_Stub的lookup方法,可以看到这个是调用了newCall方法,用于创建一个连接,接着将我们传入的字符串放入到了一个writeObject输入流中,接着就调用一个invoke方法,这个方法是一个激活的方法

来到invoke方法中,可以看到是调用了一个executeCall方法

接下来就来到StramRemoteCall的executeCall方法,这是一个客户端处理网络请求的方法

在下面可以看到在这里对成员变量in调用readByte方法进行读字节操作赋值给returnType,接着下面对returnType进行了异常处理操作,当是TransportConstants.ExceptionalReturn异常时就会调用readObject方法

可以看到in是ConnectionInputStream类型,但连接时有输入流进行对其进行赋值操作,那么可以想到如果注册中心返回的输入流是一个恶意的对象且正好触发了这个异常调用了readObject,进而实现了反序列攻击

上述的漏洞利用范围很广,要知道触发这个反序列攻击是在invoke方法中,也就是说只要哪个方法调用了 super.ref.invoke方法都会触发这个反序列化漏洞。下图就是其他方法(list、rebind、unbind)调用这个方法的地方,而这几个方法都是在RegistryImpl_Stub类下,可以在起初低版本中RMI的漏洞点之多



接着完成这个invoke请求之后,由获取了以一个输入流var2,这个var2其实也就是我们与注册中心建立连接之后注册中心返回给我们的内容,然后调用readObject方法反序列化将其读取了出来,可以看到如果我们有一个恶意的注册中心的话就可以实现反序列化攻击

总结:如上就是Client端请求注册中心的主要内容,漏洞的利用主要是在lookup获取远程对象的方法中存在了两个反序列化漏洞点,一个是RegistryImpl_Stub类的lookup方法中的,一个是StramRemoteCall的executeCall方法中的
接下来就是获取完远程对象调用方法的分析即remoteItf.Hello("rmi")

可以看到前面得到remoteItf是RemoteObjectInvocationHandler类,也就是一个动态代理类

要知道调用一个动态代理的方法,首先会调用它重写的invoke方法,因此首先会先调用RemoteObjectInvocationHandler的invoke方法,前面是进行了一些if判断用于异常处理就不多说了,最后会调用invokeRemoteMethod方法

接着发现又调用了另一个invoke方法

接着就来到UnicastRef的invoke方法,

再往下来到了marshalValue方法中,在这个方法中会对我们的value值进行writeObject序列化操作,传递给服务端,可以看到下面第三个图也就是对我们传入的参数rmi进行序列化操作



继续往下可以看到调用了StreamRemoteCall的executeCall方法,只要是客户端的请求都会调用这个方法,我们知道这个方法里面是存在反序列化攻击的,这里就不多说了

在往下看这里有一个unmarshalValue方法,如果in中有内容且不是if语句给的类型就会将其进行readObject方法,进行反序列化操作,这里是存在一个反序列化攻击点,而我们现如今呢这里是String类型会进行反序列化造作


最终调用完unmarshalValue赋值给returnValue得到rmi的转大写,完成远程对象函数的调用

到这里就分析完了,与客户端请求注册中心一样这里也有两个反序列化的点,一个是unmarshalValue方法,我们客户端在调用远程对象时所传入的参数,在unmarshalValue方法中对这个从服务端返回过来的内容进行了ReadObject反序列化操作;一个是executecall方法,这个是Client端进行网络请求时会触发,和之前的触发反序列方式是一样的,在这里面触发的协议也就是JRMP协议
分析Client端启动后注册中心的动作,其实也就是Client端调用lookup去寻找远程对象时注册端的操作

可以想一下之前在Server端分析注册中心的创建时,是不是里面有一个方法是开启了一个线程,然后等待客户端连接处理网络请求的,接下来我们就着重分析一些这个里面:这是客户端请求连接,服务端(注册中心)接受这个请求之后所作的内容
我们看到下图在创建线程t时,在最里面的一个参数是创建了AcceptLoop对象,我们进去一看究竟

要知道启动线程之后都会调用它的run方法,来到这里TCPTransport类下的run方法,在这里面会调用executeAcceptLoop方法

接下来来到executeAcceptLoop方法中,该方法是接受连接以及通过线程池处理客户端连接,connectionThreadPool.execute(new ConnectionHandler(socket, clientHost));用于处理连接的,接着我们就来到ConnectionHandler类中

来到ConnectionHandler类的run方法中,接着我们进入到下面的run0方法中

来到run0方法中,这个方法很长但可以看到就是处理一些Http请求然后读取内容,这都不是我们的重点

重要的还是handleMessages(conn, false)方法,用于读取输入流中的信息,在下图可以看到这个方法也是读取输入的字节流赋值给op,之后进行switch判断进行不同的操作

正常默认情况下会来到这个Call中,可以看到这个里面就是创建了StreamRemoteCall对象,然后就调用了serviceCall方法进行if判断

我们来到serviceCall方法中,在这里我们看到了是从我们之前创建的ObjectTable中获取了这个Taget,我们在这里打上断点,之后启动Client端

可以看到target里面的内容,这不就是之前在服务端创建好的stub以及skeleton

再往下就说获取target的dispatcher,可以看到就是UnicastSeverRef中的

在往下就来到disp.dispatch方法中

在UnicastServerRef的dispatch方法中,首先是获取了call的输入流然后读取,因为我们的skel是为RegistryImpl_Skel的,并不为空所以来到oldDispatch方法中

在oldDispatch方法中,也是想读取了输入流

再往下就来到skel.dispatch方法中

进入RegistryImpl_Skel类的dispatch方法中,这个类是.class文件,就不进行调试了,在这里可以看到是调用了switch语句根据var3的值进行不同的case操作

我们最终是来到case 2中,这里获取完输入流之后直接进行了反序列化操作,然后下面就调用了lookup方法

这里读取的输入流就是我们在Client端lookup参数,可以看到我们客户端就可以通过这个点攻击注册中心

可以除了case2之外,case3和case4都会有反序列化操作


到这分析完毕了
接下了分析Client端请求Server端时也就是String rmi = remoteItf.Hello("rmi"),Server端是如何处理的

其实和前面注册中心的差不多,因为都会进行网络请求的操作,后面都来到了serviceCall的disp.dispatchh方法

再到后面就不同了这里的skel是为null的,无法进入到if语句中进行,这里与注册中心的不一样

就会接着往下来就会获取到我们的这个方法也就是远程对象中的Hello方法


再往下就会获取参数类型以及参数值,然后调用了我们十分熟悉的unmarshalValue方法

在这里跳过一系列if判断就会来到readObject反序列化操作

调用完这unmarshalValue方法,可以看到这个params数组中第一个值就是我们从客户端传过来的rmi


再往下就是通过invoke调用这个Hello方法,然后下将得到的结果通过marshalValue序列化传递给客户端

到这里就结束了
前面在Server端创建注册中心时,发现ObjectTable表中,有一个由系统自己创建的 键值对(也就是DGC)

那么是在哪里创建的呢,其实就是在putTarget方法中,在这个方法呢就是将我们的Target放到ObjectTable表中。在下图红框圈起来的if语句中,就有调用一个DCGImpl的方法,看着像是一个日志的写入,我们进去这个类中看一眼

来到这个类中发现这个dgcLog是一个静态方法,要知道当调用一个类的静态成员变量或静态方法时,这个类就会被初始化但前提是该类尚未被初始化,也会调用这个类的静态代码块

而正好DGCImpl方法中就有一个静态代码块,可以清楚的看到他和这个注册中心的创建是一样的,创建了stub和skeleton,且最后封装到了Target中,又put到了ObjectTable表中,这就是为什么后来这个表中会有DGC的东西


在**DGCImpl_Stub**中有两个方法一个是clean,一个是dirty
在clean方法中有一个super.ref.invoke方法,前面也说过了这个里面是由反序列化漏洞的



而在dirty方法中也是有的

在**DGCImpl_Skel**中也是存在反序列化漏洞的


1、攻击客户端:
RegistryImpl_Stub#lookup->注册中心攻击客户端
DGCImpl_Stub#dirty->服务端攻击客户端
UnicastRef#invoke->服务端攻击客户端
StreamRemoteCall#executeCall->服务端/注册中心攻击客户端
2、攻击服务端
UnicastServerRef#dispatch->客户端攻击服务端
DGCImpl_Skel#dispatch->客户端攻击服务端
3、攻击注册中心
RegistryImpl_Skel#dispatch->客户端/服务端攻击注册中心