XStream是一个基于Java库,可以将Java对象序列化为XML,反之亦然。
2020年,Xstream有两个影响比较大的高危漏洞被爆出:CVE-2020-26217远程代码执行漏洞与CVE-2020-26259任意文件删除漏洞。纵观两个漏洞,他们出现的原因与机制上极其相似,因此我们在这里放到一块来分析。
首先把CVE-2020-26217与CVE-2020-26259的poc放到一起比较下:
从上图两个漏洞poc的对比上来看:二者利用链前半部分都是一样的,只有中间is元素的class属性值不同:其中一个为java.io.SequenceInputStream而另一个为com.sun.xml.internal.ws.util.ReadAllStream$FileStream。
因此,我们可以先从他们相同部分的调用链入手分析,等到了他们分歧之处,我们再分开来分析。在分析漏洞之前,我们需要搞明白poc中的元素以及其属性到底代表什么意思。
由于poc是个xml格式,我们一层层来剖析这个xml。首先把poc元素折叠起来,看看entry元素中包含的元素内容,见下图:
entry元素中包含了jdk.nashorn.internal.objects.NativeString与string两个元素
上图这样的结构代表什么意思呢?又是怎么生成的呢?
我们本地做了一个demo,看一下下面的例子:
在这个demo中,HashMap的key为一个Person对象,而value为String类型”test”
Xstream将这个map输出为下图形式
让我们对比一下poc与我们测试demo
从我们的demo与实际poc两个例子可以看出:在Xstream将Map生成xml格式数据时,会为每个Entry对象生成一个<entry>…</entry>元素,并将该Entry中的key与value作为其子元素顺次放置于其中第一个和第二个元素处。因此我们可以通过这个特点推断出,poc中jdk.nashorn.internal.objects.NativeString与string两个元素其实就是该Entry的key与value。此外,我们回头看一下我们的demo
从上图可见:在生成xml时,我们为Person对象赋值的name(“kumamon”)与age(3)属性值成为了Person对象节点(<person>…</person>)的子元素(<name>…</name>、<age>…</age>)
因此可以推断,当一个java对象通过Xstream生成xml时,其结构应遵循如下结构:
回头看一下我们的poc,我们再展开一级看看
通过上文的理解,上图poc可以理解为一个map集合,其中存在key为jdk.nashorn.internal.objects.NativeString对象、value值为test的Entry。而jdk.nashorn.internal.objects.NativeString对象又存在flags、value属性,它的flags属性值为0、value属性值为com.sun.xml.internal.bind.v2.runtime.unmarshaller.Base64Data
在弄明白poc结构之后,我们来调试下poc的解析的过程
Xstream程序在解析xml时遇到Map结构后,会新建了一个map并将xml中key-value对取出并put入其中,见下图
上图key值即为poc中Entry内key值(NativeString对象),而values则为Entry中value(test字符串)
根据map的原理可知:map在put key操作时需要获取key的hash值。因此程序调用了jdk.nashorn.internal.objects.NativeString的hashCode方法,见下图
从上图可见,程序调用了getStringValue方法,我们跟入这个方法,见下图:
在这个方法中,程序将判断this.value是否为String实例,并尝试调用this.value.toString方法
经过上文对poc的分析,此时的this.value其实就是<jdk.nashorn.internal.objects.NativeString>
…</jdk.nashorn.internal.objects.NativeString>元素中的value子元素值,攻击者可以通过xml中NativeString元素的value子元素控制。在官方给出的poc中,将value值构造为com.sun.xml.internal.bind.v2.runtime.unmarshaller.Base64Data类。见下图
因此,此时this.value为com.sun.xml.internal.bind.v2.runtime.unmarshaller.Base64Data。程序调用Base64Data类的toString方法,见下图:
Base64Data类中toString方法首先调用了其自身的get方法,跟入get方法中,见下图:
分析上图代码:this.dataHandler.getDataSource().getInputStream();将其拆分来看:
- 首先程序执行this.dataHandler.getDataSource(),即是获取Base64Data对象中dataHandler属性的DataSource值。Base64Data的dataHandler属性值以及dataHandler的dataSource属性值都可以在xml中设置。poc中将dataSource设置为:com.sun.xml.internal.ws.encoding.xml.XMLMessage$XmlDataSource。因此this.dataHandler.getDataSource()获取的值为:com.sun.xml.internal.ws.encoding.xml.XMLMessage$XmlDataSource
- 随后程序执行com.sun.xml.internal.ws.encoding.xml.XMLMessage$XmlDataSource类的getInputStream方法,这将获取com.sun.xml.internal.ws.encoding.xml.XMLMessage$XmlDataSourc的is属性值
CVE-2020-26217与CVE-2020-26259两个POC中设置的DataSource的is属性值不同,这将导致两个漏洞进入了不同的调用链。我们先来看看CVE-2020-26217
我们来看看CVE-2020-26217 的poc中DataSource元素包含的is元素是什么
通过上图可见,poc中构造的is值为java.io.SequenceInputStream
随后,程序将is变量传入readFrom方法中,见下图
readFrom方法实现如下:
此时的is变量为java.io.SequenceInputStream,随后程序调用java.io.SequenceInputStream类的read方法
从上图可见,程序将调用java.io.SequenceInputStream类的read方法中的nextStream方法,跟入nextStream方法中,见下图:
从上图110行可见,程序将执行in = (InputStream) e.nextElement();
而e的值,可以通过向xml中SequenceInputStream元素中的e元素值来控制。在poc中将这个e元素值设置为javax.swing.MultiUIDefaults$MultiUIDefaultsEnumerator,见下图
因此,程序事实上调用的是javax.swing.MultiUIDefaults$MultiUIDefaultsEnumerator的nextElement方法。接下来进入位于javax/swing/MultiUIDefaults.java中的nextElement方法
可见,这次需要执行的是iterator.next().getKey();
我们需要为javax.swing.MultiUIDefaults$MultiUIDefaultsEnumerator对象构造一个满足要求的iterator属性值。通过分析poc可知,poc中选取了javax.imageio.spi.FilterIterator作为iterator属性值,见下图:
跟入位于javax/imageio/spi/ServiceRegistry.java的javax.imageio.spi.FilterIterator类的next方法,见下图:
在javax.imageio.spi.FilterIterator类的next方法中,执行advance方法。跟入advance方法
从上图可见,程序执行了T elt = iter.next();
此时的iter显然可以通过xml中javax.imageio.spi.FilterIterator元素中iter元素控制,我们看一下poc中构造的iter子节点,见下图
当iter.next()执行后,poc中构造的java.lang.ProcessBuilder被返回并赋值给elt,见下图
随后,程序执行filter.filter(elt)
很显然,filter值是可以通过xml中javax.imageio.spi.FilterIterator元素中filter元素控制的。看一下poc
Filter赋值为javax.imageio.ImageIO$ContainsFilter类
我们跟入javax.imageio.ImageIO$ContainsFilter类的filter方法中,位于javax/imageio/ImageIO.java
可见在javax.imageio.ImageIO$ContainsFilter类的filter方法中,执行了method.invoke(elt)。method可以通过xml中javax.imageio.ImageIO$ContainsFilter元素包含的method元素控制,见poc
此时method为ProcessBuilder类的start方法,而通过上文可知:elt为构造好的java.lang.ProcessBuilder对象。在method与elt都可控的情况下,进行反射调用即可实现远程代码执行利用。
我们接下来看看CVE-2020-26259任意文件删除漏洞
首先分析下CVE-2020-26259的poc
从poc中可以发现:CVE-2020-26259的poc中is元素为com.sun.xml.internal.ws.util.ReadAllStream$FileStream,这与上一个漏洞poc不一样。
值得注意的是,这次漏洞利用的不是Base64Data中get方法里的baos.readFrom(is)这个入口,而是位于它下面一行的is.close()这行代码。通过调试,程序在执行过get方法中baos.readFrom(is)后,紧接着执行is.Close(),见下图:
此时的is是com.sun.xml.internal.ws.util.ReadAllStream$FileStream,跟入com.sun.xml.internal.ws.util.ReadAllStream$FileStream中的close方法,见下图:
当com.sun.xml.internal.ws.util.ReadAllStream$FileStream对象的tempFile属性值不为空时,删除tempFile文件。
tempFile是com.sun.xml.internal.ws.util.ReadAllStream$FileStream对象的属性值,因此可以直接在poc中com.sun.xml.internal.ws.util.ReadAllStream$FileStream元素内构造tempFile属性元素,通过tempFile属性元素控制要删除的文件,见下图poc
到此,一个任意文件删除漏洞产生了
通过对这两个漏洞的分析不难发现,CVE-2020-26259其实是CVE-2020-26217的一个思路上的延伸:在is可控时,既然baos.readFrom(is)可以利用,那么is.close()是否也能构造出一个利用链呢?从CVE-2020-26259答案上来看是可以的。
从CVE-2020-26259也可以延伸出另一个问题:只要找到一个类,其中存在close方法且close方法中有可利用的地方,那么一条新的利用链就被挖掘出来了。