Struts2 OGNL 漏洞!

這文章有漏洞影響到百度,因此先發百度,修補後,正在和劍心商量小範圍羣發各個互聯網安全團隊,結果老外也研究出,而且直接爆出這個文章的最終POC。想一想恰好明天我生日,發了,慶祝吧。身在互聯網公司安全團隊,有研究的結果,總要先保證本身安全才會往外發,這是基本原則。html

繼上次struts遠程代碼執行漏洞後,前段時間又發佈了一個遠程代碼執行漏洞。影響範圍極廣,利用方式相對上次要苛刻一點,可是讀完本文,批量抓雞不難。java

幾天前,KJ就在微博上把我賣了,咱們確實在研究這個漏洞,官方早就發公告。看到漏洞介紹後,翻閱了struts官網後,做者第一時間想到的,就是沒見過比apache更傻X的官方,struts網站沒有任何頁面有此漏洞的鏈接,憑空在那個目錄下多出個s007.html頁面(你能猜到這個地址?),若是不是看到apache的jira系統的一則留言信息,都不知道這個漏洞公告的存在。無奈的想起一個網絡流行語,以及一些其餘網絡流行語。程序員

官方公告web

咱們看看apache的jira系統中那則留言信息,做者從新描述下:ajax

到達showcase的validation的case頁面,選擇field validation頁面。正則表達式

在int類型或date類型的輸入框中,輸入數據庫

<’+#application+’> 就會在返回頁面中,顯示出struts應用程序application context中的內容(toString後)。在application中,一般會放着數據庫鏈接等重要數據,一旦被獲取的後果很嚴重。express

因爲這個東西是個ognl,因此漏洞上說,能夠執行任意ognl代碼。可是漏洞公告只給出了簡單poc,並無告訴你們怎樣執行代碼。apache

以前研究過struts的ognl執行機制,因此能很快的寫出執行任意代碼的poc來。這個不是難題,而本文的意義,在於告訴你們這個漏洞背後的技術細節。tomcat

分析補丁

在官方的公告上,已經詳細的指出了修補的代碼。

這是漏洞修補的代碼變更文件

https://issues.apache.org/jira/browse/WW-3668?page=com.atlassian.jira.plugin.ext.subversion:subversion-commits-tabpanel#issue-tabs 那幾個測試文件,就沒必要看了,也就是說,一共修改了這幾個文件

MODIFY /struts/struts2/trunk/core/src/main/resources/template/simple/text.ftl

MODIFY /struts/struts2/trunk/xwork-core/src/main/java/com/opensymphony/xwork2/interceptor/ConversionErrorInterceptor.java

MODIFY /struts/struts2/trunk/core/src/main/java/org/apache/struts2/interceptor/StrutsConversionErrorInterceptor.java

MODIFY /struts/struts2/trunk/xwork-core/src/main/java/com/opensymphony/xwork2/validator/validators/RepopulateConversionErrorFieldValidatorSupport.java 修補的手段很是的簡潔,簡潔到使人髮指,竟然用XSS的修補方式,先後加兩個引號,以後escape一下。換句話說,這樣的修補,除非你能繞過apache的escape,不然真的沒轍。做者知道以前寫過文章,說過能夠繞過escape,不過那是在特定的條件下成功,而當前這裏,是行不通的。

看不懂OGNL不要緊,咱們舉個貼近的例子,後面也會有真實代碼解析,比較複雜,不熟悉的可能看不懂,因此先用js僞代碼給你們看看原理。下面是段JAVASCRIPT代碼:

Var stringOgnlExec = ‘$url’; 原來的這段代碼中,$url,是能夠被用戶控制的,因而,攻擊者提交了

<’+#application+’> 這段就變成了

Var stringOgnlExec = ‘<’+#application+’>’; 最終,#application會被執行,並返回結果。至於那個「<」符號,和「>」符號,一點用處都沒有,能夠忽略。官方的修補代碼,也至關於對$url變量,作了EscapeJava,過濾雙引號。看到這裏,做者的心都涼了。嘗試了幾種轉義繞過,均以失敗了結。官方仍是有長進的,補丁簡單,有效(ps:我後來看了看,好像是別人建議官方這麼作得,固然也可能百度翻譯的不許確)。

漏洞原理

這些開源的系統,咱們老是能經過補丁,能夠有效的反推的出漏洞的產生和關鍵點。從上一段能夠看到,它的精髓,實際上是「注入」。

這段文字,能夠明確的指出研究方向:「User input is evaluated as an OGNL expression when there’s a conversion error」,像做者這種英語很差的人,竟然也看懂了。發生類型轉換失敗錯誤時,用戶輸入的ognl表達式,會被執行掉。這個漏洞好眼熟啊,咱們看看這個。

http://struts.apache.org/2.x/docs/s2-001.html 真是屢洞屢補啊,這是struts2.0.8時代遺留下來的老問題。當時的修補補丁,做者也看過,也研究過補丁繞過的可能性。

很慚愧,做者沒有慧眼識英雄,研究方向錯了,沒有深究下去,致使喪失良洞啊!當時的漏洞原理,是發生錯誤的時候,輸入了「%{xxxxx}」,就會執行,由於ognl會自動屢次翻譯(while語句)代碼中「%{xxx}」,當作新的一段ognl執行。而官方的修補,是把這個屢次翻譯的功能,從代碼中閹割掉了。當時的做者,就像今天的做者同樣,認爲「補丁簡單,有效」,因此也就沒想到,還能夠注入攻擊。因此,你們看了做者的文章後,千萬不要覺得做者分析完,就結束了,若是你能堅持再多分析一遍,指不定會有大驚喜,大機緣。

這裏會有個關鍵詞,叫作「validate」,struts2的世界裏,這個叫作「驗證框架」,是struts2的一個自帶插件。它的功能,從用戶可見的角度上說,是該輸入數字的時候輸錯爲字母了,這時會返回原來的頁面,顯示出一個錯誤消息,同時顯示用戶本來輸入的內容。

問題就出在「同時顯示用戶本來輸入的內容」裏,struts2驗證框架,要顯示這個內容。

一旦發生了類型轉換錯誤,就會走如下流程:

相關代碼第一步,設置ExprOverrides

/struts/struts2/trunk/xwork-core/src/main/java/com/opensymphony/xwork2/validator/validators/RepopulateConversionErrorFieldValidatorSupport.java:181行

stack.setExprOverrides(fakeParams); 這個函數放入了一個MAP,map會被後面的代碼,做爲OGNL執行。

MAP的內容來自代碼:

fakeParams.put(fullFieldName, "'" + tmpValue[0] + "'"); 很明顯看到,這裏是用單引號拼接的,能夠注入。

tmpValue[0]的值,來自

Object value = conversionErrors.get(fullFieldName); 也就是發生了類型轉換錯誤後,放入錯誤字段,和錯誤字段的值。

到了這裏,其實並不會執行ognl,只有在有去調用findValue(「」),並傳入相關錯誤字段名稱時,纔會執行對應的值內容,也就是被注入的OGNL語句。

何時會執行findvalue,而且恰好是find錯誤字段的value呢?

還得再普及一個知識,潛規則太多了,做者研究時,也發生了不少錯誤,更新了N遍文章。在條件好公司就是好,當研究成果分析發生錯誤時,馬上會有各類各樣的實際場景,供你實時分析,達到糾正錯誤的目的。嗯。阿里巴巴招聘安全工程師,果斷向我郵箱投簡歷把,你懂得。

Struts把request的getAttribute方法再次重寫,在jsp層調用的request,實際上是個struts包裝過的,並不是本來apache提供的request類。在這個方法裏:

attribute = stack.findValue(s); s是方法傳入的參數。也就是說,真正決定執行注入OGNL代碼的,是這個方法。

這個方法有多麼恐怖啊,咱們舉幾個調用此方法的地方。

一、jsp中得request.getAttribute(「kxlzx」);

這個感受還好,調用很少是吧?最起碼不如request. getParameter多。雖然你們有用到,可是很少。

二、struts標籤庫幾乎全部標籤,在獲取標籤value時,都會調用這個方法。

此次夠恐怖了,全部標籤都會用到value的時候,不然標籤意義何在。這個屬於框架自動調用,不須要開發人員參與。開發人員只須要使用struts標籤庫就好。

三、 事實上幾乎全部標籤庫,展示層,幾乎都會用這個方法從action中拿用戶提交的變量值。

粗略統計一下,包括velocity、freemarker等。

漏洞利用的條件

公告上其實給出了一個經典場景,從漏洞描述上看,這些條件,缺一不可。

一、 使用了struts驗證框架作驗證。

二、 針對可利用的字段,驗證框架作了類型轉換驗證。

三、 頁面使用了struts標籤庫。

四、 錯誤頁面恰好會顯示可利用字段的值。

很是苛刻的利用條件,其實後來證實這是個誤區,做者當初在這裏被騙了好久。回到剛剛拿到公告時,簡要的解析一下條件的苛刻性。這麼解析雖然意義不大,可是不能否認,這是開發人員會存在僥倖心理的起始。

使用了struts驗證框架作驗證

簡要說下,不使用struts驗證框架的理由。

1) 時代在發展,已經到了web2.0,新時代,你們都知道使用ajax了,輸入時候,就已經去驗證。

2) 即便是老的時代,不少開發爲了偷懶,會使用js驗證,而不適用服務端驗證,這就避免了還要寫服務端代碼。固然,有經驗的開發,會建議開發使用服務端驗證框架,由於這樣才「安全」。

3) 驗證框架使用起來,其實並不比專門寫段代碼作驗證簡單。除非是出於良好的架構考慮,纔會要求你們必定要用。

以經驗來講,做者作過一段時間的開發,驗證框架這個爛東西,能不用,基本上做者不用。

針對可利用的字段,驗證框架作了類型轉換驗證

這個必須是一個int類型,或date類型等等須要類型轉換的類型。像email驗證、string長度驗證,正則表達式驗證等等,都不在此列。難以找到一個明明應該用下拉框解決的,非要用戶輸入數字。很不友好。順便普及一下,所謂的類型轉換錯誤,就是這個字段自己是一個int類型或date等類型,可是用戶輸入了一段字母,因此錯誤了。

頁面使用了struts標籤庫

這也是一個特定條件,衆所周知,struts和webwork的標籤庫,是全部的標籤庫中,性能最讓人抓狂的幾個之一。有經驗的架構師,一般會用velocity等來替代,實在不行,也會用el表達式加jsp搞定。因此並非每一個頁面,都會啓用struts標籤庫。

錯誤頁面恰好會顯示可利用字段的值

用戶輸入錯誤,不少時候,都是一個錯誤頁面,不必定就要顯示出原來用戶輸入了什麼。

我的經驗來看,最最難以接受的,就是得找一個讓用戶輸入數字的文本框。是否是很失望呢,這個漏洞,並不像原來的那個,指哪打哪。

利用條件的減免

爲了擴大漏洞影響,必須從這四個地方下手,讓漏洞出現的概率高一點,容易利用一點。

其餘的幾點,可能真的很差撬動,可是這「針對可利用的字段,驗證框架作了類型轉換驗證」,通過研究,發現沒有想象中的難。有不少開發總抱着僥倖心理:「是否是個人項目不使用驗證框架,就沒事了」。事實上框架提供的便利,struts框架會自動對全部字段,執行類型轉換驗證,並非非要開發人員指定某個action作驗證。換句話說,這個條件徹底能夠消失了。做者寫在這裏,是由於不少人都會有這個誤區,而做者當時也走進了這個誤區,因此專門寫一下。

也就是說,其實利用條件是:

一、 發生類型轉換錯誤

二、 返回頁面會顯示錯誤字段的內容

小小的統計一下,發現會顯示錯誤字段內容的標籤,實在太多了,僅僅是struts的標籤庫裏,就能夠搜出一大把(並不全):

<s:text name="kxlzx" /> //輸出一段文字

<s:property name="kxlzx" /> //輸出一段文字

<s:textfield name="kxlzx" /> //一個input輸入框

<s:hidden name="kxlzx" /> //一個隱藏域 以上這些標籤,在項目裏,不可能不被使用。

換個場景,velocity:

$kxlzx //輸出一段文字 別說你不用這個輸出,那你乾脆不要用velocity好了

還有其餘場景,再也不一一列舉,最終得出下圖,你們能夠對照上文,從新梳理一下流程。

這真的是個好消息,做者一貫認爲:「默認的不安全,纔是最好的不安全」,「若是一個框架漏洞的產生,還須要開發人員冗長的代碼配合咱們,那是多麼的悲催」。這兩句很通順,打算申請個專利。

漏洞特徵和批量抓漏洞

這是你們最關心的地方,有不少不少的struts,怎樣可以肯定,這個系統有漏洞呢。

咱們看看showcase的頁面

這個地方,是個integer validation,咱們輸入poc,有經驗的能看出來,做者的EXP是通過處理的。這種技巧,不打算多說,後文會給出指引。

原來的地方,變成了

<true> 彈出calc,就是說,ognl被執行了。你們看懂了,得從應用程序中,找每一個input,一個一個試,從結果頁面,判斷漏洞是否存在。

然而,這只是第二步。

第一步固然是找到struts了,爲了批量的找到struts,最好的辦法就是搜索引擎。「inurl:action」,「filetype:action」都是能夠找到struts的。

爲了更加精準的找出來,做者還得提到一個struts的特性:「不分GET和POST」。這個就是傳說中得潛規則。搜索引擎去抓取的時候,一般是get方式,提交不少參數,有變態的搜索引擎,也會自動提交表單的。若是一個鏈接,進去後,頁面直接報出「Invalid field value」,這基本上,就是struts的框架驗證失敗了。

別小看這英文的短句,即便是中文的系統,若是沒有定義表單驗證中得類型轉換錯誤信息,表單中數字等類型,會自動說出驗證結果。這個自動說的結果,就是框架提供的默認信息「Invalid field value」。程序員懶得去給類型轉換失敗,寫一條專用的錯誤信息,也給咱們提供了便利。

綜合來講,咱們搜索「filetype:action Invalid field value」,就能看到N多站了。這些站,基本上都是使用了struts標籤庫的站。

攻擊步驟

再總結一下

一、 找到能夠輸入的表單,最好有日期類型,數字類型等。

二、 提交表單,修改包,把全部的表單值內容,都改成POC。

三、 查看頁面返回源碼,若是有POC執行後的返回,就成功了。

實際利用場景

Google搜索結果的第一頁,當時真的是一眼定位目標,爲何能一眼定位呢?

由於它「非死不可」!

沒錯,就是facebook。

遵循這個邏輯,黑掉「非死不可」

http://apps.facebook.com/sparechange/showTopUp.action?senderId=$sc_senderId&devId=$sc_devId&returnUrl=$sc_returnUrl&appId=$sc_appId&item=$sc_item&itemDesc=$sc_itemDesc&t=$sc_amt& 這個頁面爆出

因此提交下面表單

<form action="http://apps.facebook.com/sparechange/showTopUp.action?senderId=$sc_senderId&devId=$sc_devId&returnUrl=$sc_returnUrl&item=$sc_item&itemDesc=$sc_itemDesc&t=$sc_amt&" method="POST">

<input name=」appId」>

<input type=」submit」>

</form> 輸入:

查看源碼

<input type="hidden" name="devId" value="$sc_devId" />

<input value="$sc_senderId" name="senderId" type="hidden" />

      <input value="&#123;javax.servlet.context.tempdir=/usr/local/tomcat/work/Catalina/localhost/fb, org.apache.catalina.jsp_classpath=/usr/local/tomcat/work/Catalina/localhost/fb/WEB-INF/classes/:/usr/local/tomcat/shared/classes/:/usr/local/tomcat/shared/lib/axis.jar, org.apache.catalina.resources=org.apache.naming.resources.ProxyDirContext&#064;119015b&#125;" name="appId" type="hidden" />

      <input value="" name="flowType" type="hidden" />

其實input的value很長很長,這裏文中截斷了。 觸發這個漏洞後,做者也給facebook發了郵件。找安全團隊地址時,看到facebook竟然有賞金,很開心。可是立刻又看到apps下面的應用,不參與懸賞,那個不爽啊,雖然出於職業道德,仍是給它發了郵件,真不爽。只能自我安慰一下:「哥郵件裏寫的英文,大家真的覺得能當英文看麼?「

從側面撬動苛刻條件

最後討論一下「錯誤頁面恰好會顯示可利用字段的值」,這一條仍是從側面,能夠撬動的。搜索引擎的結果裏,不少都是頁面上有「Invalid field value」,其實並不會在當前頁面展現字段的內容,也就沒法觸發漏洞了。

當前頁面真的沒法觸發漏洞麼?

真的。。。-_-!

別忙潑水,做者要說得是,只是當前頁面不會觸發而已。出現這個錯誤,表明了這個系統的開發人員,喜歡struts,喜歡struts的標籤庫。你懂麼?他好這口兒!

咱們來看看另外一個例子。

這位雖然沒有非死不可,可是想百毒不侵,那是沒有的。

猜到了?百度!

搜索引擎裏看到了一個url:

http://wm123.baidu.com/site/detail.action?siteId=1..

很不爽的頁面,這個頁面沒有input,不會顯示它的值,也就不會調用getAttribute。可是它告訴我,百度的開發,使用了struts標籤庫。

因此,很不幸(固然做者其實以爲很幸運),在另外一個地方,找到了想要的東西。提交下面表單

POST http://beidou.baidu.com/tool/addGroupClone.action?task.userid=3701034 HTTP/1.1

struts.token.name=groupCloneToken&groupCloneToken=fdasfdsafdsa&list-filter=&sub-list-filter=&task.groupIdList=2201994&task.unitstate=0&task.copyRegion=true&task.copyNetWork=true&task.copyDirectType=true&task.planid=-1&task.newPlan=true&task.planname=fdsafdsafdsa&task.budget=' %2B #application %2B ' 就能看到一個使人驚喜的結果。

直接提交是不行的,裏面有token,一次驗證馬上失效。必須找到頁面,在發送到服務器以前,攔截http請求,修改後提交,才能看到結果。

看到這裏,你們都會批量抓雞了。關於執行任意代碼的exp怎麼寫,這個時候固然不會放出來,否則過些天,還有誰會關注個人微薄(http://t.qq.com/javasecurity)呢?想知道的,請把做者以前寫過的《Struts2和Webwork遠程命令執行漏洞分析》「http://www.inbreak.net/archives/167」,認真讀幾遍,會有大機緣。做者當時看到漏洞介紹後,1分鐘以內,寫好了執行任意代碼的EXP。只要你懂,你就懂了。

相關文章
相關標籤/搜索