前言
本来是想复现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
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?
· 【译】Visual Studio 中新的强大生产力特性
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
· 【设计模式】告别冗长if-else语句:使用策略模式优化代码结构