序列化的問題貌似在最近爆發的非常頻繁,最近有小伙伴在問我關于這兩天爆發的Xstream組建的反序列化的漏洞,最近公司非常忙,不過趕上周末剛好抽時間看了下,其實這次的漏洞和之前JRE的那個反序列化漏洞觸發的條件基本上差不多,不過關于JRE的那個序列化似乎沒人關注,有興趣的同學可以去找找關于那個JRE的序列化,影響力不亞于11月份我分析的那個Apache CommonsCollection的漏洞。好了,回到正文吧。在分析Xstream漏洞時發現,XStream漏洞的根源在于Groovy組件的問題,其實在15年的時候有人給Groovy報了一個CVE-2015-3253的Bug,不過網上似乎沒有太多細節,為什么這次分析XStream的漏洞的時候要提到Groovy的那個CVE,因為漏洞的根源就來自于那個CVE。
先來說說那個Groovy的CVE-2015-3253的漏洞吧。
網上貌似沒有對該漏洞的分析,所以只能通過cve的連接去看看具體是什么問題,http://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2015-3253,官方的描述如下:
The MethodClosure class in runtime/MethodClosure.java in Apache Groovy 1.7.0 through 2.4.3 allows remote attackers to execute arbitrary code or cause a denial of service via a crafted serialized object.
通過上述漏洞描述信息,我們知道了問題大概出現在了MethodClosure類上,該類定義以及方法如下圖
該類的描述為Represents a method on an object using a closure which can be invoked at any time,大概意思就是通過構建一個指定對象以及調用方法的Closure的實例并且可以在任何時候進行調用。上圖紅色線標記的方法即為觸發構建好的對象以及指定方法的函數,我們跟進看看該方法最終是怎么樣執行的。
通過該方法的注釋可以知道該方法的作用為調用指定對象的指定方法,所以MethodClosure類中構造方法中的兩個參數的意思為owner代表調用方法的對象,method為調用方法的名字,所以我們可以構造特定了對象從而實現執行特定函數,我們自己定義的對象以及方法最終會調用上圖中紅色框標記的函數進行執行。
舉個例子,例如我們想通過MethodClosure實現執行命令的功能,那么代碼如下
#!java
MethodClosure mc = new MethodClosure(new java.lang.ProcessBuilder("open","/Applications/Calculator.app"), "start");
mc.call();
注:這里調用的call方法最終會調用doCall函數,有興趣的可以自己去調試。
這樣上述代碼就可以實現代碼執行,關于該函數的功能我們基本上搞明白了,那么我們回過頭來想想,難道這個CVE就是說了下這個函數可以執行特定代碼么?
既然我們知道了如何構建以及觸發相關函數從而導致代碼的執行,那么我們不妨去找找看看那些函數調用了存在缺陷的函數,通過eclipse我們可以很容易看出那些地方調用了MethodClosure#call()函數
如上圖所示,我們可以看到groovy.util.Expando類的hashcode以及toString等方法調用了MethodClosure#call()函數,到這里從事java的小伙伴們應該比較激動,這里的hashCode()方法調用了存在缺陷的函數,hashCode函數才是這個CVE比較核心的地方,首先我們需要知道hashCode函數的作用,當兩個對象比較是否相等的時候,會調用該對象的hashCode以及equals方法進行比較,如果這兩個方法返回的結果一致,那么認為這兩個對象是相等,如果被調用對象沒有重寫hashCode以及equals方法,那么會調用父類的默認實現。
這里明白hashCode的作用之后,再來說說HashMap的put方法,該方法的定義如下
因為Map是一種key-value類型的數據結構,所以Map集合不允許有重復key,所以每次在往集合中添加鍵值對時會去判斷key是否相等,那么在判斷是否相等時會調用key的hashCode方法,如果我們精心構造一個groovy.util.Expando對象作為Map集合的key,那么在將對象添加進集合時就會觸發groovy.util.Expando的hashCode方法,從而觸發我們的惡意代碼。
明白上面的知識后我們再來跟進groovy.util.Expando#hashCode方法,看看如何精心構造一個一刻執行惡意代碼的對象,如下圖
這里從上圖中可以看出調用getProperties().get("hashCode")
方法從而實現自定義的hashCode,我們只需要調用setProperties("hashCode",Expando實例)
去綁定hashCode屬性對于的實現就行了,這里hashCode必須是Closure或者其子類才能最終調用call函數,MethodClosure類恰好是Closure的子類,所以結合這兩個地方,惡意代碼就會成功觸發。
上面說到過通過調用Map#put
方法即可觸發我們構造好的代碼,那么有人可能會問了,那些場景下才會觸發Map的put方法,在反序列化時這樣的場景還是存在的,除了這次的Xstream反序列化之外java的其他反序列化類中很可能也是有這樣的場景的。
下面給出利用代碼
Xstream的反序列化漏洞的根源就是上文所述的Groovy組件的問題,只不過在Xstream中進行反序列化時恰好有觸發存在缺陷函數的點,也就是Xstream在反序列化時調用了Map#put函數將構造好的Expando實例作為key添加到集合中時觸發了代碼執行,如下圖
這里的key就是我們構造的Expando的實例對象。
在構造EXP時,首先我們要構造一個Expando的一個對象實例,同時設置hashCode的實現為MethodClosure的實例,然后實例化一個HashMap對象調用put方法將Expando的實例化對象作為key,value任意添加到map中,然后讓Xstream對map進行序列化,這樣我們的Payload就OK了,
估計有很多人不明白漏洞作者博客的POC是怎么來的,這里的序列化是基于xml的,所以得借助Xstream相關函數將構造好的對象進行序列化然后生成xml,反序列化時解析xml,轉換成相關對象。
好人做到底,我就把POC的生成代碼也發出來吧
執行程序后,我們的POC就生成成功,如下圖所示
至于怎么執行其他的代碼,這個還比較麻煩,除了執行命令之外,好像沒有什么好的辦法,因為MethodClosure的構造函數中指定了要執行方法的對象以及執行的方法名稱,所以說只能調用一次構造函數,并且有一個無參數的方法可以執行,這樣才能實現函數的正常運行。
這個漏洞的成因應該是兩方面的共同造成了,一方面等待Xstream官方的補丁,此外Groovy在2.4.3之后修復了代碼執行的這個bug,禁用了MethodClosure的代碼執行功能。
受影響的用戶可以通過升級Groovy的版本來緩解該漏洞造成的影響。