前言

本来是想复现weblogicCVE-2017-3248漏洞的,这个漏洞使用了ysoserial的jrmp模块,开启rmi监听进行通信的,达到执行任意反序列化 payload。下文有些概念比较晦涩难懂,建议查阅其他资料配合使用。

JRMP协议是什么

JRMP全称为Java Remote Method Protocol,也就是Java远程方法协议。
一个RMI的过程,是用到JRMP这个协议去组织数据格式然后通过TCP进行传输,从而达到RMI,也就是远程方法调用。
JRMP是一个协议,是用于Java RMI过程中的协议,只有使用这个协议,方法调用双方才能正常的进行数据交流。

分布式垃圾收集(DGC)

在Java虚拟机中,对于一个本地对象,只要不被本地Java虚拟机中的任何变量引用,它就可以被垃圾回收器回收了。

而对于一个远程对象,不仅会被本地Java虚拟机中的变量引用还会被远程引用。如将远程对象注册到Rregistry时,Registry注册表就会持有它的远程引用。

RMI框架采用分布式垃圾收集机制(DGC,Distributed Garbage Collection)来管理远程对象的生命周期。DGC的主要规则是,只有当一个远程对象不受任何本地引用和远程引用,这个远程对象才会结束生命周期。

当客户端获得了一个服务器端的远程对象存根时,就会向服务器发送一条租约通知,告诉服务器自己持有这个远程对象的引用了。此租约有一个租约期限,租约期限可通过系统属性java.rmi.dgc.leaseValue来设置,以毫秒为单位,其默认值为600 000毫秒。如果租约到期后服务器端没有继续收到客户端新的租约通知,服务器端就会认为这个客户已经不再持有远程对象的引用。

因此可以通过与DGC通信的方式发送恶意payload让服务端进行反序列化,从而执行任意命令。
具体来讲,这里利用RMI,让受害机反序列化UnicastRef这个类,使该类发起一个JRMP连接到恶意服务端上,从而在DGC层造成一个反序列化,因为DGC层是在反序列化之后进行设置的,所以之前设置的黑名单过滤没有作用。

ysoserial exploit/JRMPListener

下面我们分析ysoserial 的JRMPListener,在idea中导入ysoserial项目
运行JRMPListener的时候我们需要设置参数:
在这里插入图片描述
在JRMPListener的main方法中打上断点,debug运行,我们调试一下。
在这里插入图片描述

获取第二个参数和第三个参数,我们跟进一下:
第一个参数是payload种类,也就是Gadget,第二个参数是Gadget的参数。
在这里插入图片描述
这里getObject()就是获取利用链的函数,然后return一个gadgetObject。
在这里插入图片描述
接着往下走就是接收第一个参数,也就是我们开需要开启的监听端口,然后实例化JRMPListener方法,我们看一下这个类的构造函数:
在这里插入图片描述
创建一个socket端口,端口为7777。
然后就是c.run()开启了本地监听,接下来的run函数会一直监听。
在这里插入图片描述
然后我们JRMPClient发一下payload,JRMPClient在下面也会讲。
在这里插入图片描述
JRMPClient会用随机端口,与我们的监听的端口进行socket连接。
在这里插入图片描述

关键代码是在JRMPListener.run.doMessage.doCall,若是接收到请求,先判断请求类型,若为DGC则会将payload返回出去。

payloads/JRMPClient

JRMPListener就是做监听作用,JRMPClient,顾名思义就是客户端去请求服务端,idea的启动参数如下:
在这里插入图片描述
从main函数进来,走到run函数,
在这里插入图片描述
在这里插入图片描述
在这里,run函数调用JRMPClient类的getObject方法当作参数。跟进getObject方法。
在这里插入图片描述
断点处构造了反向连接所需要的host/port实例化成te对象,然后就是创建了一些反向连接需要的对象,这里有兴趣可以去跟一下rmi注册的底层源码。之后把ref传入了RemoteObjectInvocationHandler中,最后进行了一个动态代理,返回了一个proxy对象。
proxy对象发送到我们的JRMPListener的时候,服务端就会把这个proxy对象反序列化,进而执行利用链的命令。

我们在看看反序列化这个proxy对象,会怎么处理。代码来到run()函数的38行。
在这里插入图片描述
跟进:
在这里插入图片描述
我们看到他会走到RemoteObject的Object函数,为什么会走到这里呢,因为proxy的反序列化其实就是调用其InvocationHandler的readObject方法,而这里的InvocationHandler则是RemoteObjectInvocationHandler。

RemoteObjectInvocationHandler的readObject方法是调用其父类RemoteObject的readObject方法
if判断后会走到这里:
在这里插入图片描述
跟进readExternal,这是UnicastRef的readExternal方法,调用了LiveRef.read():
在这里插入图片描述
跟进LiveRef.read():
在这里插入图片描述
这里调用了DGCClient.registerRefs方法 ,这方法里面,产生了与JRMP服务端的交互。
在这里插入图片描述
这里是静态调用,看看这里面有个doWhile循环,先执行了DGCClient.EndpointEntry.lookup,再执行var2.registerRefs(var1)。
如图第47行就是开启与JRMPListener的Socket通信
第48行就是处理服务端过来的请求,造成了反序列化
跟进DGCClient#registerRefs,在最后调用了DGCClient$EndpointEntry的makeDirtyCall方法
在这里插入图片描述
跟进makeDirtyCall方法,到205行,调用了DGCImpl_Stub#dirty
在这里插入图片描述
跟进DGCImpl_Stub#dirty(这里有个问题,这个DGCImpl_Stub其实是动态生成的类,无法调试,所以只能进源码直接看)
在这里插入图片描述
newCall是和 JRMPListener建立连接,write写入序列化数据,invoke用来处理服务端的数据,最后readObject来反序列化(前提是传过来的数据不是一个异常类)

简单总结就是,当客户机创建(序列化)远程引用时,DGC调用 dirty()。当客户机完成远程引用后,它会调用对应的 clean() 方法。

这里面大致的流程是使用TCPEndpoint和DgcID创建了LiveRef对象,之后生成了DGCImpl_Stub代理对象,最后开启了与JRMPListener的Socket通信。其实不需要这么了解底层原理,知道大致用法和大致作用即可

参考:
https://www.anquanke.com/post/id/225137
https://blog.csdn.net/whatday/article/details/106971531
https://www.cnblogs.com/yyhuni/p/15091121.html
https://www.cnblogs.com/nice0e3/p/14280278.html
https://www.cnblogs.com/yyhuni/p/15069427.html
posted on 2022-09-13 18:13  noone52  阅读(102)  评论(0编辑  收藏  举报