Java 反序列化漏洞的利用有两个条件。首先是漏洞点,也就是将攻击者可控的内容传递给 ObjectInputStream.readObject() 函数的调用链;另一个条件是gadget,也就是从某个类的反序列化入口函数 readObject() 开始,一步步执行到危险函数的调用链。
WebLogic 对于 T3 协议和 IIOP 协议的处理,天然就会进行反序列化的漏洞点。因此,对于 WebLogic 反序列化漏洞的挖掘,主要就是在 gadget 的寻找和补丁绕过上。
2020年的1月、4月和7月, WebLogic 先后爆出了三个一脉相承的反序列化 CVE,涉及了七个 gadget。下面简单分析一下这三个 CVE 以及相关的 gadget。
CVE-2020-2555
2020年1月,CVE-2020-2555 被公开。这个反序列化 gadget 有三条利用链。
首先都是利用了 JDK 中的 BadAttributeValueExpException。这个类的特点是可以将对 readObject() 的调用,转换成对 toString() 函数的调用。

BadAttributeValueExpException 的这个特性,可以显著扩大反序列化 gadget 的范围,因此反序列化利用工具 ysoserial 中,有五条利用链都使用这个类作为入口。

经过从 readObject() 到 toString() 的转换之后,找到真正的入口函数:LimitFilter.toString()。

函数中调用的两处 extracotor.extract() 函数来自接口 ValueExtractor.

搜索一下这个接口函数的实现,共29个。

CVE-2020-2555 的第一个调用链,利用了 ChainedExtractor 和 ReflectionExtractor 的两个 extract() 函数实现。


这两个实现可以完美的串起一条利用链,和 ysoserial 里 CommonsCollections1 利用链中所使用的 ChainedTransformer.transform() 和 InvokerTransformer.transform() 几乎一模一样。


因此我们可以构造出 POC,基本原则是:
1、使用
BadAttributeValueExpException作为反序列化的入口类,从而调用到toString()2、使用
LimitFilter对象作为前者的valObj,从而调用到extract()3、使用
ChainedExtractor作为LimitFilter的m_comparator,从而可以进行链式extract()。4、使用
ReflectionExtract构建ChainedExtractor,从而可以链式调用method.invoke()从而成功调用Runtime.getRuntime().exec()。
最终的调用栈如下:

CVE-2020-2555 的第二条利用链,同样来自上面 29 个 ValueExtractor.extract() 的实现类之一:MvelExtractor。

熟悉 MVEL 的你应该一眼就看出了利用方法,只要使用 MvelExtractor 替换掉前一个利用链的 3、4两步就可以了。最终调用栈如下:

第三条利用链,同样来自上面 29 个实现类之一:MultiExtractor。

由于 MultiExtractor.extract() 函数中没有链式调用,因此我们可以将 MultiExtractor 作为连接第一条利用链中 LimitFilter.toString() 和 ChainedExtractor.extract() 的桥梁。LimitFilter.toString() 间接通过 MultiExtractor.extract() 调用到 ChainedExtractor.extract() 中。最终的调用栈如下:

至于修复补丁,Oracle 打在了 LimitFilter.toString() 函数里。这个修复很神奇,仅仅封锁了三条调用链的入口,而从 MultiExtractor.extract() 经过 ChainedExtractor.extract() 调用到 ReflectionExtractor.extract() 的利用链、以及MvelExtractor.extract() 的利用链依然存在,只要再找一个入口就好了。
CVE-2020-2883
2020 年 4 月,CVE-2020-2883 被公开。同样的三条利用链,只是更换了入口函数。
前面说到,入口函数 LimitFilter.toString() 被修补,我们需要寻找一个新的入口。这个新入口同样可以在反序列化的时候,调用到 ValueExtract.extract() 中。
很快,大神们就找到了:ExtractorCompartor.compare()。

ExtractorComparator.compare() 其实是对 jdk 中 Comparator.compare() 这个接口函数的实现。

那么,怎么从 readObject() 调用到 Comparator.compare() 函数呢? ysoserial 早就给出了答案:PriorityQueue。

调用链如下:
PriorityQueue.readObject()
-> PriorityQueue.heapify()
-> PriorityQueue.siftDown()
-> PriorityQueue.siftDownUsingComparator()
-> Comparator.compare()
现在,我们将 CVE-2020-2555 的三条利用链稍加改造,就能实现 CVE-2020-2883 三条新的利用链:
1、使用
PriorityQueue代替BadAttributeValueExpException作为反序列化的入口类,从而通过readObject()调用到compare()2、将
ExtractorComparator对象设置为PriorityQueue的comparator属性值,从而通过compare()调用到extract()3、将
ChaninedExtractor或MvelExtractor或MultiExtractor设置为PriorityQueue的队列元素,从而通过extract()调用到目标函数method.invoke()或MVEL.excuteExpression()
这样就能顺利绕过 CVE-2020-2555 的补丁修复,构成了三条换汤不换药的新利用链。
以第一条利用链为例,最终的调用栈如下。

至于修复补丁,Oracle 并没有封禁利用链条上的 PriorityQueue 和 ExtractComparator ,只是将 ReflectionExtractor 和 MvelExtractor 放到了反序列化黑名单中。
仔细看下 CVE-2020-2883 的几个调用栈,从 PriorityQueue.readObject() 到 ExtractorComparator.compare() 再到 ValueExtractor.extract() 的利用链仍然存在,所以只需要在 29 个实现类中再找一个新的利用类就可以完成不定的绕过。
CVE-2020-14645
腾讯蓝军很快就找到了新的可利用的实现类 UniversalExtractor。


只是这里在调用 method.invoke() 时存在限制条件,函数名称必须是 get或 is 起始。因此可以利用那些已知的 Json 反序列化 gadget 链进行攻击。最终的调用栈如下:

心得
通常寻找反序列化 gadget,不论是用工具搜索还是手工进行,我们会将 readObject() 作为 source,将那些危险函数(如method.invoke()、Runtime.exec()、FileOutputStream.write()等)作为 sink进行查找。但其实,在 Java 纷繁复杂的各种依赖库中,已经存在了许许多多的代码链片段可以利用。例如 BadAttributeValueExpException 将 toString() 纳入了利用链,PriorityQueue 将 compare() 纳入了利用链,ExtractorComparator 将 extract() 纳入了利用链,等等等等。在搜索的时候,将这些扩展出的利用链作为 source 或 sink,会大大增加搜索的范围,也很可能会发现新的世界。
另一方面,对于漏洞的修复者而言,并不是堵住了入口就算修复了漏洞,而是要全方位封锁调用链上的方方面面,否则就会向 Oracle 一样留下永远补不完的 CVE。