从 WebLogic 一脉相承的三个反序列化 CVE 说起

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() 函数的调用。

01-badattributevalue

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

02-ysoserial-badattribute

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

03-limitfilter

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

04-valueextractor

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

05-extract-implementation

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

06-chainedExtractor

07-reflectionExtractor

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

08-chainedTransformer

09-invokerTransformer

因此我们可以构造出 POC,基本原则是:

  • 1、使用 BadAttributeValueExpException 作为反序列化的入口类,从而调用到 toString()

  • 2、使用 LimitFilter 对象作为前者的 valObj,从而调用到 extract()

  • 3、使用 ChainedExtractor 作为 LimitFilterm_comparator,从而可以进行链式 extract()

  • 4、使用 ReflectionExtract 构建 ChainedExtractor,从而可以链式调用 method.invoke() 从而成功调用 Runtime.getRuntime().exec()

最终的调用栈如下:

10-callstack

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

11-mvelExtractor

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

12-callstack-mvelExtractor

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

13-multiExtractor

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

14-callstack-multiExtractor

至于修复补丁,Oracle 打在了 LimitFilter.toString() 函数里。这个修复很神奇,仅仅封锁了三条调用链的入口,而从 MultiExtractor.extract() 经过 ChainedExtractor.extract() 调用到 ReflectionExtractor.extract() 的利用链、以及MvelExtractor.extract() 的利用链依然存在,只要再找一个入口就好了。

CVE-2020-2883

20204 月,CVE-2020-2883 被公开。同样的三条利用链,只是更换了入口函数。

前面说到,入口函数 LimitFilter.toString() 被修补,我们需要寻找一个新的入口。这个新入口同样可以在反序列化的时候,调用到 ValueExtract.extract() 中。

很快,大神们就找到了:ExtractorCompartor.compare()

15-extractorComparator

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

16-jdk-comparator

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

17-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 对象设置为 PriorityQueuecomparator 属性值,从而通过 compare() 调用到 extract()

  • 3、将 ChaninedExtractorMvelExtractorMultiExtractor 设置为 PriorityQueue 的队列元素,从而通过 extract() 调用到目标函数 method.invoke()MVEL.excuteExpression()

这样就能顺利绕过 CVE-2020-2555 的补丁修复,构成了三条换汤不换药的新利用链。

以第一条利用链为例,最终的调用栈如下。

18-callstack-extractComparator

至于修复补丁,Oracle 并没有封禁利用链条上的 PriorityQueueExtractComparator ,只是将 ReflectionExtractorMvelExtractor 放到了反序列化黑名单中。

仔细看下 CVE-2020-2883 的几个调用栈,从 PriorityQueue.readObject()ExtractorComparator.compare() 再到 ValueExtractor.extract() 的利用链仍然存在,所以只需要在 29 个实现类中再找一个新的利用类就可以完成不定的绕过。

CVE-2020-14645

腾讯蓝军很快就找到了新的可利用的实现类 UniversalExtractor

19-UniversalExtractor

20-UniversalExtractor-extractCommplex

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

21-callstack-universalExtractor

心得

通常寻找反序列化 gadget,不论是用工具搜索还是手工进行,我们会将 readObject() 作为 source,将那些危险函数(如method.invoke()Runtime.exec()FileOutputStream.write()等)作为 sink进行查找。但其实,在 Java 纷繁复杂的各种依赖库中,已经存在了许许多多的代码链片段可以利用。例如 BadAttributeValueExpExceptiontoString() 纳入了利用链,PriorityQueuecompare() 纳入了利用链,ExtractorComparatorextract() 纳入了利用链,等等等等。在搜索的时候,将这些扩展出的利用链作为 sourcesink,会大大增加搜索的范围,也很可能会发现新的世界。

另一方面,对于漏洞的修复者而言,并不是堵住了入口就算修复了漏洞,而是要全方位封锁调用链上的方方面面,否则就会向 Oracle 一样留下永远补不完的 CVE