作者:深信服千里目實驗室
原文鏈接:https://mp.weixin.qq.com/s/WtT0o2ygGGQek3wvIulfiQ

1. 組件介紹

XStream是Java類庫,用來將對象序列化成XML(JSON)或反序列化為對象。XStream在運行時使用Java反射機制對要進行序列化的對象樹的結構進行探索,并不需要對對象作出修改。XStream可以序列化內部字段,包括私private和final字段,并且支持非公開類以及內部類。在缺省情況下,XStream不需要配置映射關系,對象和字段將映射為同名XML元素。但是當對象和字段名與XML中的元素名不同時,XStream支持指定別名。XStream支持以方法調用的方式,或是Java標注的方式指定別名。XStream在進行數據類型轉換時,使用系統缺省的類型轉換器。同時,也支持用戶自定義的類型轉換器。

XStream類圖:

2. 高危漏洞介紹

漏洞名稱 漏洞ID 影響版本 CVSS
XStream 遠程代碼執行漏洞 CVE-2013-7285 XStream <= 1.4.6 9.8
XStream 遠程代碼執行漏洞 CVE-2019-10173 XStream < 1.4.10 9.8
XStream 遠程代碼執行漏洞 CVE-2020-26217 XStream <= 1.4.13 8.0
XStream 外部實體注入漏洞 CVE-2016-3674 XStream <= 1.4.8 7.5
XStream 拒絕服務攻擊 CVE-2017-7957 XStream <= 1.4.9 7.5

XStream組件漏洞主要是java反序列化造成的遠程代碼執行漏洞,目前官方通過黑名單的方式對java反序列化攻擊進行防御,由于黑名單防御機制存在被繞過的風險,因此以后可能會再次出現類似上述java反序列化漏洞。

3. 漏洞利用鏈

3.1 組件風險梳理

根據XStream組件的漏洞,結合XStream常用的使用場景,得到如下風險梳理的場景圖。

3.2 利用鏈總結

基于風險梳理思維導圖,總結出一種漏洞的利用場景。

3.2.1 無權限 -> GetShell


XStream遠程代碼執行漏洞單獨使用,即可完成GetShell。一般情況,如果一個Web應用中使用了受漏洞影響版本的XStream,都會受XStream本身的漏洞影響。除此之外,由于XStream是將XML格式數據反序列化成對象。因此如果Web應用還引入了其他的在反序列化過程中容易出現安全問題的依賴,也會出現反序列化漏洞。

4. 高可利用漏洞分析

從高危漏洞列表中,針對部分近年高可利用漏洞進行漏洞深入分析。

技術背景

java動態代理

Java標準庫提供了一種 動態代理(Dynamic Proxy) 的機制:可以在運行期動態創建某個interface的實例。

例子: 我們先定義了接口Hello,但是我們并不去編寫實現類,而是直接通過JDK提供的一個Proxy.newProxyInstance()創建了一個Hello接口對象。這種沒有實現類但是在運行期動態創建了一個接口對象的方式,我們稱為動態代碼。JDK提供的動態創建接口對象的方式,就叫動態代理。

package test3_proxyclass;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

public class Main {
    public static void main(String[] args) {
        InvocationHandler handler = new InvocationHandler() {
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                System.out.println(method);
                if (method.getName().equals("morning")) {
                    System.out.println("Good morning, " + args[0]);
                }
                return null;
            }
        };
        Hello hello = (Hello) Proxy.newProxyInstance(
                Hello.class.getClassLoader(), // 傳入ClassLoader
                new Class[] { Hello.class }, // 傳入要實現的接口
                handler); // 傳入處理調用方法的InvocationHandler
        hello.morning("Bob");
    }
}

interface Hello {
    void morning(String name);
}

java動態代理機制中有兩個重要的類和接口InvocationHandler(接口)和Proxy(類),這一個類Proxy和接口InvocationHandler是我們實現動態代理的核心;

InvocationHandler接口: proxy代理實例的調用處理程序實現的一個接口,每一個proxy代理實例都有一個關聯的調用處理程序;在代理實例調用方法時,方法調用被編碼分派到調用處理程序的invoke方法。

newProxyInstance: 創建一個代理類對象,它接收三個參數,我們來看下幾個參數的含義:

loader:一個classloader對象,定義了由哪個classloader對象對生成的代理類進行加載
interfaces:一個interface對象數組,表示我們將要給我們的代理對象提供一組什么樣的接口,如果我們提供了這樣一個接口對象數組,那么也就是聲明了代理類實現了這些接口,代理類就可以調用接口中聲明的所有方法。
h:一個InvocationHandler對象,表示的是當動態代理對象調用方法的時候會關聯到哪一個InvocationHandler對象上,并最終由其調用。

getInvocationHandler: 返回指定代理實例的調用處理程序

getProxyClass: 給定類加載器和接口數組的代理類的java.lang.Class對象。

isProxyClass: 當且僅當使用getProxyClass方法或newProxyInstance方法將指定的類動態生成為代理類時,才返回true。

newProxyInstance: 返回指定接口的代理類的實例,該接口將方法調用分派給指定的調用處理程序。

4.1 XStream 遠程代碼執行漏洞

1 漏洞信息

1.1 漏洞簡介

  • 漏洞名稱:XStream Remote Code Execution Vulnerability
  • 漏洞編號:CVE-2013-7285
  • 漏洞類型:Remote Code Execution
  • CVSS評分:CVSS v2.0:7.5 , CVSS v3.0:9.8
  • 漏洞危害等級:高危

1.2 漏洞概述

包含類型信息的流在unmarshalling時,會再次創建之前寫入的對象。因此XStream會基于這些類型信息創建新的實例。攻擊者可以操控XML數據,將惡意命令注入在在可以執行任意shell命令的對象中,實現漏洞的利用。

1.3 漏洞利用條件

1.4 漏洞影響

影響版本:
XStream <= 1.4.6

1.5 漏洞修復

獲取XStream最新版本,下載鏈接:https://x-stream.github.io/download.html

2.漏洞復現

2.1 環境拓撲


2.2 應用協議

8080/HTTP

2.3 環境搭建

基于Windows平臺,使用環境目錄下的xstreamdemo環境,拷貝后使用Idea打開xstreamdemo文件夾,下載maven資源,運行DemoApplication類,即可啟動環境。效果如圖。

2.4 漏洞復現

運行sniper工具箱,填寫表單信息,點擊Attack,效果如圖。

3.漏洞分析

3.1 詳細分析

3.1.1 代碼分析

傳入的payload首先會在com.thoughtworks.xstream.XStreamfromXML()方法中處理,在進入unmarshal()方法中進行解集。

com.thoughtworks.xstream.core.AbstractTreeMarshallingStrategy類中的unmarshal()方法中調用start()方法進行Java對象轉換。

com.thoughtworks.xstream.core.TreeUnmarshaller類中的start()方法通過調用readClassType()獲取type類型。

readClassType()方法中調用readClassAttribute方法。

進入readClassAttribute方法調用aliasForSystemAttribute方法獲取別名。調用getAttribute方法,獲取reader對象中記錄的外部傳入XML數據中是否存在對應的標簽,如果不存在則返回null。

回到HierarchicalStreams#readClassType方法中調用realClass方法,通過別名在wrapped對象中的Mapper中循環查找,獲取與別名對應的類。

找到sorted-set別名對應的java.util.SortedSet類,并將類存入realClassCache對象中。

回到TreeUnmarshaller#start方法,調用convertAnother方法。進入convertAnother方法后,調用defaultImplementationOf方法,在mapper對象中尋找java.util.SortedSet接口類的實現類java.util.TreeSet

獲取java.util.TreeSet類型,調用lookupConverterForType方法,尋找對應類型的轉換器。進入lookupConverterForType方法,循環獲取轉換器列表中的轉換器,調用轉換器類中的canConvert方法判斷選出的轉換器是否可以對傳入的type類型進行轉換。

轉換器TreeSetConverter父類CollectionConvertercanConvert方法判斷,傳入的type與java.util.TreeMap相同,返回true,表示可以使用TreeSetConverter轉換器進行轉換。

回到DefaultConverterLookup#lookupConverterForType方法,將選取的converter與對應的type存入typeToConverterMap。

回到TreeUnmarshaller#convertAnother方法中,調用this.convert方法。

首先判斷傳入的xml數據中是否存在reference標簽,如果不存在,則將當前標簽壓入parentStack棧中,并調用父類的convert方法。

進入convert方法中,調用轉換器中的unmarshal方法,對傳入的xml數據繼續解組。

首先調用unmarshalComparator方法判斷是否存在comparator,如果不存在,則返回NullComparator對象。

根據unmarshalledComparator對象狀態,為possibleResult對象賦予TreeSet類型對象。

由于possibleResult是一個空的TreeMap,因此最終treeMap也是一個空對象,從而調用treeMapConverter.populateTreeMap方法。

進入populateTreeMap方法中,首先調用調用putCurrentEntryIntoMap方法解析第一個標簽,再調用populateMap方法處理之后的標簽(此流程中二級標簽只存在一個,因此在處理二級標簽時暫不進入populateMap方法)。

具體調用流程如下,com.thoughtworks.xstream.converters.collections.TreeSetConverter類中調用putCurrentEntryIntoMap方法 -> com.thoughtworks.xstream.converters.collections.AbstractCollectionConverter.readItem() 中的 readClassType()方法獲取傳入xml數據中標簽名(別名)對應的類(與本節中獲取sorted-set對應類的流程相同)。本次獲取的是dynamic-proxy對應的java.lang.reflect.Proxy.DynamicProxyMapper類型,并將別名與類型作為鍵值對,存入realClassCache中。

回到AbstractCollectionConverter.readItem()方法中,調用convertAnother方法,尋找DynamicProxyMapper對應的convert,獲取到DynamicProxyConverter轉換器。

得到com.thoughtworks.xstream.mapper.DynamicProxyMapper$DynamicProxy,按照之前獲取轉換器之后的流程,調用轉換器中的unmarshal()方法獲取interface元素,得到java.lang.Comparable,并添加到mapper中。

在通過循環查詢,繼續查找下面的節點元素,進而獲得了handler java.beans.EventHandler

調用Proxy.newProxyInstance方法創建動態代理,實現java.lang.Comparable接口。

調用convertAnother方法獲取傳入type的轉換器,java.beans.EventHandler對應的convert是ReflectionConverter。并將父類及其對象寫進HashMap中

com.thoughtworks.xstream.converters.reflection.AbstractReflectionConverter.duUnmarshal()方法獲取下面的節點元素target java.lang.ProcessBuilder
具體流程如下:調用getFieldOrNull方法,判斷xml格式中傳入的標簽名在目標類中是否存在對應屬性。

在調用reader.getNodeName()方法獲取標簽名,并賦值給originalNodeName。

調用realMember方法獲取反序列化屬性的名稱。

調用readClassAttribute方法獲取target標簽中傳入的類名

調用realClass獲取上述過程中類名對應的類,并調用unmarshallField方法進行解析。

進入方法中,尋找對應type的轉換器,由于是java.beans.EventHandler作為動態代理的實現類,所以選擇的轉化器都是ReflectionConverter 。使用選中的轉換器進行解組。

使用ReflectionConverter convert處理java.lang.ProcessBuilder ,在duUnmarshal()方法獲取command標簽和comand標簽下的String標簽及其參數。(其中String標簽下的參數是在下一層convert調用中獲取的。)

調用this.reflectionProvider.writeField方法,將參數值傳入對象中。


在按照獲取target標簽相同的流程獲取action標簽,最終將start方法存入對象中。

回到TreeMapConverter#populateTreeMap方法中,上述過程中構造的object保存在sortedMap中。且其中的動態代理實現的接口是java.lang.Comparable,因此只要調用java.lang.Comparable接口中的compareTo方法,即可觸發動態代理,進入java.beans.EventHandler實現類中的invoke方法。在populateTreeMap方法中調用putAll方法,將sortedMap中的對象寫入result變量的過程中會調用到compareTo,調用鏈如下。

進入java.beans.EventHandler#invoke方法中,通過反射執行對象中的方法。

3.1.2補丁分析

XStream1.4.7版本中,在com.thoughtworks.xstream.converters.reflection.ReflectionConverter添加type != eventHandlerType阻止ReflectionConverter解析java.beans.EventHandler類。從而防御了此漏洞。

4.2 XStream 遠程代碼執行漏洞

1.漏洞信息

1.1 漏洞簡介

  • 漏洞名稱:XStream Remote Code Execution Vulnerability
  • 漏洞編號:CVE-2019-10173
  • 漏洞類型:Remote Code Execution
  • CVSS評分:CVSS v2.0:7.3 , CVSS v3.0:9.8
  • 漏洞危害等級:高危

1.2 漏洞概述

包含類型信息的流在unmarshalling時,會再次創建之前寫入的對象。因此XStream會基于這些類型信息創建新的實例。攻擊者可以操控XML數據,將惡意命令注入在在可以執行任意shell命令的對象中,實現漏洞的利用。

1.3 漏洞利用條件

1.4 漏洞影響

影響版本:
XStream = 1.4.10

1.5 漏洞修復

獲取XStream最新版本,下載鏈接:https://x-stream.github.io/download.html

2.漏洞復現

2.1 環境拓撲


2.2 應用協議

8080/HTTP

2.3 環境搭建

基于Windows平臺,使用環境目錄下的xstreamdemo環境,拷貝后使用Idea打開xstreamdemo文件夾,下載maven資源,運行DemoApplication類,即可啟動環境。效果如圖。

2.4 漏洞復現

運行sniper工具箱,填寫表單信息,點擊Attack,效果如圖。

3.漏洞分析

3.1 詳細分析

3.1.1 代碼分析

CVE-2019-10173漏洞與CVE-2013-7285漏洞原理相同,由于在XStream的安全模式默認不啟動,導致防御失效。

Xstream 1.4.7對于漏洞的防御措施: 通過在com.thoughtworks.xstream.converters.reflection.ReflectionConverter添加type != eventHandlerType阻止ReflectionConverter解析java.beans.EventHandler

Xstream 1.4.10漏洞產生原因:com.thoughtworks.xstream.converters.reflection.ReflectionConverter類中,canConvert方法中的type != eventHandlerType被刪除了,使得原來的漏洞利用方式可以再次被利用。 由于在Xstream1.4.10中的com.thoughtworks.xstream.XStream類增加了setupDefaultSecurity()方法和InternalBlackList轉換器,通過黑名單的形式對漏洞進行防御。但是安全模式默認不開啟,必須在初始化后才可以使用,eg:XStream.setupDefaultSecurity(xStream)。導致防御失效,造成漏洞的第二次出現。

3.1.2補丁分析

XStream1.4.11版本中,在com.thoughtworks.xstream.XStream更改安全模式初始化方法中的securityInitialized標志位。在調用InternalBlackList轉換器中的canConvert方法時,可以進行黑名單匹配,從而防御了此漏洞。

漏洞防御 在Xstream1.4.11中的com.thoughtworks.xstream.XStream類中InternalBlackList類會對java.beans.EventHandler進行過濾,java.beans.EventHandler執行marshal或者unmarshal方法時,會拋出異常終止程序。

4.3 XStream 遠程代碼執行漏洞

1.漏洞信息

1.1 漏洞簡介

  • 漏洞名稱:XStream Remote Code Execution Vulnerability
  • 漏洞編號:CVE-2020-26217
  • 漏洞類型:Remote Code Execution
  • CVSS評分:CVSS v2.0:無, CVSS v3.0:8.0
  • 漏洞危害等級:高危

1.2 漏洞概述

包含類型信息的流在unmarshalling時,會再次創建之前寫入的對象。因此XStream會基于這些類型信息創建新的實例。攻擊者可以操控XML數據,將惡意命令注入在在可以執行任意shell命令的對象中,實現漏洞的利用。

1.3 漏洞利用條件

1.4 漏洞影響

影響版本:
XStream = 1.4.13

1.5 漏洞修復

獲取XStream最新版本,下載鏈接:https://x-stream.github.io/download.html

2.漏洞復現

2.1 環境拓撲


2.2 應用協議

8080/HTTP

2.3 環境搭建

基于Windows平臺,使用環境目錄下的xstreamdemo環境,拷貝后使用Idea打開xstreamdemo文件夾,下載maven資源,運行DemoApplication類,即可啟動環境。效果如圖。

2.4 漏洞復現

運行sniper工具箱,填寫表單信息,點擊Attack,效果如圖。

3.漏洞分析

3.1 詳細分析

3.1.1 代碼分析

代碼分析:傳入的payload首先會在com.thoughtworks.xstream.XStreamfromXML()方法中處理,在進入unmarshal()方法中進行解集。

com.thoughtworks.xstream.core.AbstractTreeMarshallingStrategy類中的unmarshal()方法中調用start()方法進行Java對象轉換。

com.thoughtworks.xstream.core.TreeUnmarshaller類中的start()方法通過調用readClassType()獲取type類型。

readClassType()方法中調用readClassAttribute方法。

進入readClassAttribute方法調用aliasForSystemAttribute方法獲取別名。調用getAttribute方法,獲取reader對象中記錄的外部傳入XML數據中是否存在對應的標簽,如果不存在則返回null。

回到HierarchicalStreams#readClassType方法中調用realClass方法,通過別名在wrapped對象中的Mapper中循環查找,獲取與別名對應的類。

DefaultMapper中,通過反射,獲取到string標簽傳入的class,并將類存入realClassCache對象中。

回到TreeUnmarshaller#start方法,調用convertAnother方法。進入convertAnother方法后,調用lookupConverterForType方法,尋找對應類型的轉換器。進入lookupConverterForType方法,循環獲取轉換器列表中的轉換器,調用轉換器類中的canConvert方法判斷選出的轉換器是否可以對傳入的type類型進行轉換。

轉換器ReflectionConvertercanConvert方法判斷,傳入的type非null,返回true,表示可以使用ReflectionConverter轉換器進行轉換。

回到DefaultConverterLookup#lookupConverterForType方法,將選取的converter與對應的type存入typeToConverterMap。

回到TreeUnmarshaller#convertAnother方法中,調用this.convert方法。

首先判斷傳入的xml數據中是否存在reference標簽,如果不存在,則將當前標簽壓入parentStack棧中,并調用父類的convert方法。

com.thoughtworks.xstream.converters.reflection.AbstractReflectionConverter.duUnmarshal()方法獲取下面的節點元素iter java.util.ArrayList$Itr
具體流程如下:調用getFieldOrNull方法,判斷xml格式中傳入的標簽名在目標類中是否存在對應屬性。

在調用reader.getNodeName()方法獲取標簽名,并賦值給originalNodeName。

調用realMember方法獲取反序列化屬性的名稱。

調用readClassAttribute方法獲取iter標簽中傳入的類名

調用realClass獲取上述過程中類名對應的類,并調用unmarshallField方法進行解析。

進入方法中,尋找對應type的轉換器,使用選中的ReflectionConverter轉換器進行解組。

使用ReflectionConverter convert處理java.util.ArrayList$Itr ,在duUnmarshal()方法獲取cursor標簽和cursor標簽下的參數。(調用unmarshallField方法,與上述流程相似)

調用this.reflectionProvider.writeField方法,將參數值傳入對象中。


回到AbstractReflectionConverter#doUnmarshal方法中獲取后續的標簽及其參數(分別為lastRet,expectedModCount,outer-class)。

按照同樣的反序列化流程獲取屬性值,并寫入對象。

解析outer-class標簽,由于type是java.util.ArrayList,選擇轉換器是CollectionConverter

調用CollectionConverter#unmarshal方法進行反序列化。

調用CollectionConverter#populateCollection -> CollectionConverter#addCurrentElementToCollection->AbstractCollectionConverter#readItem方法。最終調用realClass方法獲取type類,獲取過程中將outer-class標簽下的子標簽存入realClassCache中。

回到AbstractCollectionConverter#readBareItem方法調用convertAnother方法,按照之前的流程進行反序列化,為屬性賦值,并寫入對象。

最終返回ProcessBuilder對象,寫入FilterIterator對象中。

在按照獲取java.util.ArrayList$Itr對象相同的流程獲取javax.imageio.ImageIO$ContainsFilter對象,通過反序列化為其內部的method屬性和name屬性進行賦值。

在選擇轉換器的過程中,由于method屬性的類型是java.lang.reflect.Method,因此選擇對應的轉換器為JavaMethodConverter。

調用JavaMethodConverter#unmarshal方法進行xml數據解析,獲取java.lang.processBuilder類中的start方法對象,寫入到javax.imageio.ImageIO$ContainsFilter對象中。

再按照相同的流程,將start方法名寫入name屬性中。

最終在調用ReflectionProvider#writeField方法將javax.imageio.ImageIO$ContainsFilter對象寫進FilterIterator對象的filter屬性中。

FilterIterator對象返回給最初的iterator對象中。

調用iterator.next()方法時,會調用其實現類FilterIterator中的next方法。

進入調用advance方法,調用filter方法時,會通過反射執行ProcessBuilder對象中的start方法,從而造成代碼執行。

3.1.2補丁分析

XStream1.4.11版本中,在com.thoughtworks.xstream.XStream更改安全模式初始化方法中的securityInitialized標志位。在調用InternalBlackList轉換器中的canConvert方法時,可以進行黑名單匹配,從而防御了此漏洞。

漏洞防御 XStream1.4.14版本中,在com.thoughtworks.xstream.XStream的黑名單添加java.lang.ProcessBuilderjavax.imageio.ImageIO$ContainsFilter。從而防御了此漏洞。

5.漏洞利用

XStream 遠程代碼執行漏洞

漏洞利用視頻,請轉到原文觀看,鏈接:https://mp.weixin.qq.com/s/WtT0o2ygGGQek3wvIulfiQ

6.參考鏈接

1.https://blog.csdn.net/yaomingyang/article/details/80981004
2.https://github.com/x-stream/xstream/compare/XSTREAM_1_4_6...XSTREAM_1_4_7
3.https://github.com/x-stream/xstream/compare/XSTREAM_1_4_10...XSTREAM_1_4_11
4.https://github.com/x-stream/xstream/compare/XSTREAM_1_4_13...XSTREAM_1_4_14


Paper 本文由 Seebug Paper 發布,如需轉載請注明來源。本文地址:http://www.bjnorthway.com/1417/