鄭國祥,安恒信息技術有限公司Web安全研究員。從事web安全研究——漏洞挖掘,源碼審計,web防御。2015年獲得騰訊的TSRC漏洞之王,2016年活的御洞圣杰的稱號。
Struts2的基本的工作原理
Struts2提交HTTP的請求,進行Action Map的映射,解析URL,解析之后,進行任務鏈的調度,把某一個URL請求映射進來。這個會加載進來,解析完成之后,進行Map鏈式的存儲。處理完成,進行Action的一個調用,Action調用完成,通過返回結果,查找到對應內容,返回對應的模板。模板處理完成之后,再調用后處理的攔截器,內容處理完封裝成一個Response,反饋給用戶,
Struts2的漏洞歷史
從2007年的時候S2-001,后面像S2-002、S2-003等等,到現在是S2-046。Struts2的001到046過程當中,所有的嚴重漏洞大家可以看到有這么幾個。這是2007年的S2-001,問題出現在驗證表單的時候,如果這個表單內容驗證失敗的話會執行一個表達式,導致一個代碼執行。后面就是2008年的時候,S2-003,它是在一個參數攔截器里面產生了一個OGNL表達式的執行。2010年S2-005,它繞過了之前的那種防護方式,執行了一些OGNL的表達式,導致了一些代碼的執行。S2-008默認的情況下開了一個DevMod。2012年的S2-009和2013年的S2-013,還有2013年7月份的S2-016,大家比較熟悉的就是2013年的S2-016,因為這個漏洞當時產生的影響是非常巨大的,包括很多政府金融等一些產業的互聯網的廠商,都因為這個漏洞造成了一些損失。大概在4年之后,就是今年的4月份,我們安恒發現了一個S2-045,可能會比之前S2-016等等那些漏洞危害性更大,因為它覆蓋的版本及范圍可能會更廣泛。它影響的范圍,比如說是2.3.5,其實2.3.5是在S2-016里面的,在S2-016受影響的,可能也會受S2-045或者是S2-046的影響。后面比如說2.3.5到2.3.10,后面就是一個2.5到2.5.10。我們看2.5到2.5.10的話,2.5后面基本上沒有出過什么比較嚴重的漏洞,后面包括2.5到2.5.10是公認的比較安全的,這次可能基本上都淪陷了。它主要的問題就是,因為在處理一個文件上傳請求的時候,一個文件上傳的請求程序異常,被Struts2捕獲了,就會做一個解析。
S2-045的漏洞危害
攻擊者可以制造一些惡意的HTTP數據包,利用這個漏洞在受影響的數據庫上執行任意的系統命令。一般系統畫像,很多用戶基本上都是有了這個漏洞,基本上直接一丟過去就可以拿到權限,包括造成了一些拒絕服務、數據泄漏,或者是網站上一些數據的提取等等。而且這個漏洞不像之前的漏洞,很多銀行的網站開了一個DMI的話,產生了一個遠程代碼執行,但是S2-045、S2-045不需要任何前置條件,也不需要提供任何插件。文件上傳加包,默認是在Struts2的框架里面的,所以說這個漏洞是非常嚴重的。
安恒Sumap統計
我們對全網做了統計,美國、中國等Struts2的使用量非常巨大在爆發的時候,有一部分是在S2-016的影響下,可能修復了S2-016,因為后面沒有出現比較大的漏洞,基本上不會去升級。現在出了S2-045,Struts2都是有問題的,除了S2-001,所以這個影響是非常巨大的,這邊基本上都是紅色的狀況。這是中國區范圍的分布,比如浙江、四川等等一些地方,雖然使用量非常小,在銀行里面會使用得比較廣泛。
這是S2-045漏洞修復后的一個圖。大概爆發之后,基本上修復廠商出補丁也是很快的,后面基本上都修復了。修復之后,這邊的顏色對比可能會淺了很多。但是量還是非常大的,還是存在大量的有漏洞的機器。這邊是中國的一個顯示,比如像浙江的、廣東的,四川的是明顯的減少了,這邊是很紅的一片,現在顏色都變淡了。
如何挖掘此類漏洞
這里針對的是Struts2的一個介紹。首先可以利用Fuzz的一些技術,一個HTTP的請求包,包括請求方式,還有后面跟著是一個請求URL,后面就是一個HTTP的協議版本,下面就是一個HTTP的一個頭,后面跟著就是一個對應。這些是正常的請求,邊上對應的像Reponse200的話,就是對應的是Response。這是一個HTTP的Post的請求,有些會在包里面有很多的數據。這是一個Post的一種形式,可以通過這種方式做一些文件的上傳,包括有一些跟原始的不太一樣的。如果沒有加文件名的話就是一個普通的表單,但是如果加上文件名的話,就是一個文件類型的。
我們大致了解了HTTP請求大致是什么格式之后,我們可以想辦法做一些Fuzz。我們可以選一些Payload,我們知道表達式里面的一些Payload基本上都是通過百分號,一個大括號,這是下面0.UNIQUEID是一個普通的,像Method:,這也是Struts2里面支持的一些特性,這是參數前綴。還有Action,現在可能不支持了,現在可能支持上面兩種,還有一些其他類型也可以加進去。為什么要加0.UNIQUEID,后面可能會講到。基本上Fuzz-Payload就是這些,也可以自己加。所有的Payload可以自己加。
我們要利用Java Jvmti技術去Hook,在解析OGNL表達式的時候產生一些異常。之前Fuzz的Payload里面加了一個0.,這樣做的方式就是為了讓OGNL解析這個表達式的時候產生異常。產生這個異常的時候,會把這個異常的一些字符串,包括0.UNIQUEID,就是一個特征值,會把字符串拋出來,表達式里面異常的是一個語法異常的類,而我們可以通過某種方式,Hook掉之后,因為出異常的都會去調這樣一個類。所以說我們把這個類提取出來之后,我們可以去判斷這個異常的信息里面是否包含之前講的這樣一個特征,就是UNIQUEID,如果包含的話,我們就可以認為這個請求過來,我們可以獲取到一些惡意的Payload,可以進入到我們的表達式里面執行,這樣的話,我們就可以拿來做一些利用。包括我們分析整個鏈路,拿到這個鏈路請求之后,我們可以做一些簡單的服務。
這是我們之前發現S2-045的一個Fuzz的結果。這邊有一個Struts2的版本,之前測試的時候在2.3.31上做的一個Fuzz,包括Fuzz的Payload是怎么樣的,這邊顯示的是發現了一個Struts2的,下面是一個SEQ的特征,我們可以看到原始的請求數據包。下面是一個函數的調用站,這個請求到漏洞觸發點整個函數的調用流程是怎么樣的,我們都可以從這個函數的調用站里面找到。我們可以看一下,這是之前Fuzz出來的一個解析請求的類。就是因為這邊會做一個解析,會把HTTP的請求包調用出來之后,去做一個請求包的解析。如果出現異常的話,他會把這個異常捕獲起來。捕獲起來的話,會去封裝一個錯誤的消息類。這個錯誤的消息類里面的一些拋出來的異常里面的那些值,都是被我們可以控制的。這個控制又丟給OGNL表達式去執行,直接造成一個遠程代碼執行。
根據這個異常的報錯信息,我們在跑Struts2應用的時候都會有一些日志信息,我們可以根據日志信息去查找到底是哪些加包出問題了。這邊就是一個標準的調用站的錯誤鏈,比如說這邊應該是一個文件Base的那一類,再做解析,HTTP請求的時候,導致了這樣一個類。根據這個類,我們可以去查找,這是一個類里面的,這里顯示有一種情況下,會拋一個可控的異常點。這樣的話,我們就可以利用這種方式直接做一些代碼的執行。
碰到這種情況,我們還可以去考慮另外一種情況。并不是只有這種情況下才能拋出可控的異常,我們可以把完整的一些源碼下載下來之后,可以通過走所Throw New等方式,我們可以把所有的異常出來的點都搜一遍,我們可以看哪些異常的錯誤信息可以控制。比如說這邊就有一個其實可以被當作我們可以控制的那些異常點來做處理,丟給表達式去執行。還有一個上傳上來一個文件,文件名里面如果包含了一個0字節的時候,就會拋一個可控的異常點。這兩種情況下,后面是可以控制的S2-046的Bypass的這種情況。
我們之前的步驟僅僅只是找到了那些可以利用的一些輸入點。比如說Struts2里面默認有一個Security Member Access,就算你拿到了一個執行的權限,比如執行一些系統命令,這些是有Security Member Access的保護,我們要突破這些防護。這里是做了一個黑名單,包括哪些Class是無法加載的,OGNL里面的一些內容,下面OGNL的一些Access,包括在S2-032的時候,因為利用了Bypass的技術,就加入了白名單里面了。還有黑名單的一些包名,包括一些Java的一些,還有幾個類可以Bypass之前的一個黑名單的防護,這些都加進去了。但是我們看了這些黑名單之后,就必須要想辦法去突破這些黑名單的防護。
首先我們可以來關注一下OGNL表達式里面用的最多的就是一個OGNL Context。這個里面有好幾個Map里面,OGNL Context里面就是一個Map,有好幾個屬性,像一些字符串等等。這些字符串我們拿來根本就沒法用,沒法修改,比如像Context里面的一些屬性,我們可以看到有一個屬性可以控制。但是這個Context是屬于第9個屬性,這個屬性在OGNL表達式里面,Context維護了所有的Struts2的一個框架的類。我們可以看一下,這邊就是一些黑名單的類,Context沒有在黑名單的那些包里面或者類里面。我們可以看一下什么是Context,翻譯成中文的意思就是一個容器,通常來講,它支持了Struts2的一些運行環境,包括哪個類需要哪些屬性,或者哪個類需要哪些對象,可以通過Container注射進去,可以寫一個標簽一樣的東西,包括還包含了一些第一例的條件控制,包括Struts2運行起來是否是DEV的模式,包括像Security Member Access初始化限制的一些類,就是之前在這個PPT里面介紹的這些,都是通過Context的方式注冊進去的。我們可以看到,這邊就是一個Context Build簡單的構造,包括Struts ?DEV很多的屬性,也會加載Struts里面的一些默認的屬性,包括哪些類可以加載,哪些類不可以加載,都是在Container的一個容器里面。
了解了Container之后,我們了解一下Security Member Access的初始化過程。我們可以看到,所有的表達式通過工廠模式來創建的。我們可以看到,比如這里有一個方法,OGNL Value Stack,可以把一些屬性注冊到Container里面去,包括還有一個把Allow Static Method Acess的屬性,注冊過來,有這樣一個標簽一樣的內容。這邊是OGNL Value Stack的一個類,會構建一個防護。我們可以看到,在過程當中的一些內容,包括設置一個黑名單,就是Security Member Access黑名單設置一些屬性,包括哪些類或者哪些包下載一些類,都不允許調用的,這個屬性值都是通過OGNL這個類里面獲取的。
這邊是一個OGNL Util里面,Struts-Default.xml里面,就是一個單域模型,表示只有一個OGNL Util的識別,我們只要控制這個,就可以控制Security Member Access里面的一些包括黑名單的包名,黑名單的一系列東西。因為Container又提供了不在黑名單里面的,我們可以通過操控Container,獲取OGNL Util的對象,通過這個對于,我們可以把里面的一些東西設置為空,這樣的話,Security Member Access這些黑名單屬性就相當于是空的。我們所有的一些限制類,包括像Java、反射的一些屬性都是可以繞過,可以直接去調用的。
這邊Security Member Access的EXP,是繞過防護限制的。首先會定義一個Default Member的Access,一種是老版本里面的Struts2,有一個Security Member,我們只需要去覆蓋Security Member Access就可以,這是老版本里面的情況。還有一種就是在新版本里面,在OGNL Container里面沒有這個對象的應用,所以說我們需要通過剛才講的那種方式,通過Container去獲取OGNL Until之后,再把這個OGNL Unitl里面的一些黑名單、包名抹掉,再設置回去。然后再通過Security Member Access去覆蓋原始的Security Member Access。這樣的話,Defaul Member就是為了允許方法的調用,可以直接調用Jave里面其執行一些系統的命令。這里就是兼容了老版本跟新版本的一個情況。
以后Struts2可能還會出現S2-047、S2-048,大家可以對OGNL表達式解析的語法做一些Hook,可以做監控,做Fuzz,基本上可以Fuzz出來。包括一些邏輯上的問題,比如說可能在某個Action里面應用了某個類,但是在Struts2內部,某些地方也通過某種方式去獲取屬性,獲取到這些屬性之后,直接用OGNL表達式進行,就可以控制這個表達式。挖掘這種漏洞可能需要一些時間,還有應急方面的成份。
上一篇:騰訊徐少培:瀏覽器地址欄之困
下一篇:陳鐘:主持人