本文首发于奇安信攻防社区:https://forum.butian.net/share/573
漏洞原理
漏洞的利用链前半部分和CVE-2020-26217相同,思路也很像,都是利用Base64Data.get方法,替换一个恶意的InputStream,区别在于之前的入口被封了,因此使用PriorityQueue+ObservableList$1的方法调用到Base64Data的toString。需要注意的是ObservableList这个类只有较高版本JDK会创建内部匿名的compartor。xstream的反序列化漏洞一个很重要的特点是可以利用没有实现Serializable接口的类。
利用链如下
1 | PriorityQueue.readObject() |
核心的利用点在于ByteArrayOutputStreamEx的 readFrom()方法,该方法while(true)进行条件判断
1 | public void readFrom(InputStream is) throws IOException { |
在read函数中,会计算count-pos值,并且如果len大于这个值时,就会判断这个值是否大于0,如果小于等于0则返回0,这里本来的作用是是计算是否已经读完数据。但是如果将pos设置为int类型的最小值,count设置为0,那么count-pos则会等于pos。这和int类型在Java中的存储方式有关,java中最大的正数是2147483647,存储成二进制格式就是011111111111111111111111111111111,而如果此时再加1,则会变成10000000000000000000000,这其实是Java中int类型最小值-2147483648所对应的二进制数,同样的,0减一个负数,实际的数值等于这个负数取反并加一, 10000000000000000000000取反为011111111111111111111111111111111,再加一就又变成了最小的负数。从而陷入了readFrom的循环中。
POC构造
在poc的构造中,如果试图直接从结果入手,演算xstream的处理流程,会比较复杂,简单的方法是构造出对应的对象,然后再调用toXML即可获得恶意XML文本。构造的过程如下:
确定利用链→构造出对应的对象→调用toXML方法
Source
首先确定一个可以利用的入手点,漏洞作者找到的是PriorityQueue,xstream会调用这个类的readObject方法。
Gadget
在PriorityQueue的利用中,需要找一个危险的comparator。作者找的这个comparator很独特,它是一个匿名类,藏在javafx.collections.ObservableList这个类的一个方法中,在Java中,就算是匿名类,在运行中也会存储一个类名,按照在代码中的定义顺序,在外部类名后增加$1、$2等依次排序。
1 | public default SortedList<E> sorted() { |
在这个类中只有这一个匿名类,因此这个匿名类的名字就是javafx.collections.ObservableList$1,利用Class.forName可以找到这个类。接下来是实例化这个类,在Xstream中对类的实例化使用的是Unsafe类的allocateInstance方法,这个方法可以在不使用构造方法的情况下直接实例化对象,这里构造poc也同样可以使用这个方法。
这个类的compare调用了比较对象的toString方法,下一步是找一个可用的toString方法,作者使用的是Base64Data这个类,这个类不是第一次出现了,在Xstream2020年爆出的3个漏洞中都使用了它。由于入口被封,因此这里换了一个方法进行触发。它的特点是toString→get→ByteArrayOutputStreamEx.readFrom()
Slink
ByteArrayOutputStreamEx.readFrom()中使用了while(True)读取数据,is.read()返回0,则循环永远不会结束。