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
。