Author:LJ(知道創宇404安全實驗室)

Date: 2017-03-14

0x00 概述

凌晨12點,Struts2 官方發布了 S2-045 的漏洞公告,仔細閱讀描述,發現是友商安恒的 Nike Zheng 小伙伴提交的,所以趕緊跟進分析了一把。

0x01 漏洞場景還原

首先,我們獲取最新的 Struts2 源代碼,并編譯出存在漏洞環境的 demo ,這里我選擇了官方教程推薦的 struts2-showcase , 具體操作指令如下:

git clone https://github.com/apache/Struts.git
cd Struts
git checkout STRUTS_2_5_10
mvn package

國內的網絡,你懂的...,最后在 apps/showcase/target 目錄下生成了 struts2-showcase.war 文件,將其拷貝至 tomcat 的webapps 目錄下,為了輸入方便,這里將文件名重命名為 showcase.war 。啟動tomcat,訪問 http://localhost:8080/showcase/ 可以看到漏洞環境的界面如下:

漏洞環境首頁

下面是使用網上流出 PoC 的關鍵部分打一下看到的效果:

漏洞效果

從上面的效果圖可以看出:

  • 發起請求的時候并不需要找到具體的上傳點,只要是有效的 URL 就可以

  • 發起請求不需要 POST 方法也可以觸發,因為 curl 默認發起請求就是 GET

0x02 漏洞分析

先進入剛才獲取到的 Struts 源代碼目錄,并查看diff后的代碼,具體操作如下:

git diff STRUTS_2_5_10 STRUTS_2_5_10_1 > s2-45.diff

在去除了一些配置和測試文件信息后,下面這部份代碼引起了筆者的注意:

diff代碼

從刪除的代碼上可以看出專門對 validation 做了不為空的校驗,進一步跟進 LocalizedTextUtilfindText 函數,發現這個函數被重載:

LocalizedTextUtil代碼

在392行代碼中 ActionContext 中獲取了 ValueStack ,這里可以簡單認為是從請求的上下文環境當中獲取了所有的數據,然后調用重載的下一個 findText ,在729行代碼中看到 TextParseUtil 類對 valueStack 做了轉換處理,做進一步的跟進,具體代碼如下:

TextParseUtil代碼

translateVariables 的多次調用后,最終調用了第152行的 translateVariables 函數,根據此函數的注釋,就是在這里做了由值到對象并執行的轉換,最終觸發漏洞。

但是這里有一個問題,就是具體是在哪里觸發的漏洞,這個是由 Struts2 的機制引起的,在 struts2 的框架當中,內置了許多的攔截器,主要用于對框架的功能擴展,此次漏洞的 FileUploadInterceptor 就是對框架文件上傳的功能擴展。

0x03 動態分析

在進一步的跟進調試前,我們先梳理下 Struts2 整體的框架結構,以便于理解 request 請求流轉的過程,這里借用 struts 官方的設計架構圖,并做個整體架構的簡述:

Struts2 官方架構圖

我們先不看 Struts2 自帶的藍色核心部分,可以看到 request 請求大部分都是在淺黃色的過濾器和綠色的攔截器中間流轉。

在有了具體的業務邏輯需要后臺處理時,需要將 request 發送到對應的 Action 來處理,圖中畫的是 FilterDispatcher ,但在 Struts 2.5 以上已經將這個換成了 StrutsPrepareAndExecuteFilter

看到上圖,去除 Struts2自帶的核心部分,可以看到大部分的過程都是在過濾器和攔截器中間流轉,而且對流轉的Action并沒有加以區分。

簡單來說,過濾器對所有的請求都起到作用,主要用來對請求添加,修改或者分派轉發至Action處理業務邏輯,圖中的FilterDispatcher 就是起到這個作用的。

攔截器能對配置文件中匹配的 request 進行處理,并能獲取 request 當中的上下文環境及數據。

我們先來對 FileUploadInterceptor 下斷點來調試:

其中第264行是對錯誤的request進行驗證處理,我們跟進findText 看一下:

這里 request 獲取ValueStack對象,我們可以看到 defaultMessage 的值如下圖:

進一步跟進 findText 函數,就發現這個函數被重載了。重載的函數始于448行,然后是循環操作。跳出循環單步跟進到573行,我們發現這里又調用了 GetdefaultMessage 方法:

于是繼續跟進 GetDefaultMessage方法并定位到729行:

接著跟進 translateVariables 方法,expression 就是傳入的錯誤信息: 注意到上圖使用了 ognl"$""%"標簽,兩者都能告訴執行環境 ${}%{} 中的內容為ognl表達式PoC中使用的是 "%",使用 "$" 也能觸發漏洞。 再次跟進translateVariables方法,在這里提取出ognl表達式并調用evaluate方法執行。

最后程序在 parserevaluate方法中執行了 ognl表達式。

綜上,漏洞觸發后程序的調用棧先后順序如下:

1.intercept:264,FileUploadInterceptor           文件上傳攔截器處理報錯信息
2.findText:393,LocalizedTextUtil
3.findText:573,LocalizedTextUtil                提取封裝的Action中的值棧
4.getDefaultMessage:729,LocalizedTextUtil       值到對象轉換
5.translateVariables:45,TextParseUtil
6.translateVariables:123,TextParseUtil
7.translateVariables:166,TextParseUtil          提取出ognl表達式并執行
8.evaluate:13,OgnlTextParser                    最后執行ognl表達式

0x04 總結

回顧struts2歷史RCE漏洞,形成原因與ognl表達式執行相關的漏洞層出不窮。 當閱讀這些歷史漏洞的分析文章時,有些作者以 ”這里竟然執行了ognl表達式“ 這句話對漏洞點進行吐槽。 然而這次的漏洞成因則更為奇怪,解析出錯誤信息的ognl表達式并執行。 最后,當動態調試此類漏洞時,前往 ognl表達式執行的方法處下斷點調試,馬上就能一目了然的看到漏洞觸發的完整調用棧。

0x05 參考鏈接


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