前言
到目前位置struts2的漏洞編號已經到了S2-057,一直想系統的學習下Struts2的漏洞,但由於工作量較大,一直擱淺。最近由於工作需要,借此機會來填下坑。個人認為一個框架漏洞出來了僅僅看下別人分析的文章是遠遠不夠,因為這些文章往往都只針對個別漏洞,可能框架中還存在類似的漏洞你依然發現不了。所以我說需要系統的學習下,從框架的源碼開始分析它的工作流程(當然這里我會有所取舍,全部都講沒意義),同時這樣也會加深自己對該框架的理解,之后如果一個新的漏洞出來了,你可以僅根據官方的公告或變動的代碼很簡單地還原整個漏洞,同時這樣做對代碼審計也會有一定的幫助。這是我struts2系列文章的第一篇,篇幅會比較長(實際上分析源碼的地方我已經省了很多)。之后我還會寫spring、tomcat等系列的漏洞分析文章。
准備工作
我使用的是eclipse+struts-core2.1.6,struts2的各版本是由些許區別的,但是大致流程都是相同的,這里采用較老的版本是因為S2早期的漏洞都可以在里面找到,方便分析。
Struts2的工作流程
在原生的jsp+servlet項目中,常用會到Filter過濾器來過濾一些參數等等,這里struts2就是將自己的核心過濾器配置在web.xml中,這樣可以讓指定的HTTP請求都經過Struts2。早期struts2的核心過濾器是FilterDispathcer (org.apache.struts2.dispatcher.FilterDispatcher),但是struts2>=2.1.3之后就變為了StrutsPrepareAndExecuteFilter,而StrutsPrepareAndExecuteFilter在配置的時候也經常會分開為StrutsPrepareFilter和StrutsExecuteFilter,這是方便開發者更加靈活的使用,配置信息如下:
Filter的執行順序是然配置順序來的,所以這里我們先從StrutsPrepareFilter開始分析。
一個Filter比較重要的兩個方法,一個是init一個doFilter,init是Filter初始化的時候才會調用,一般是容器剛啟動時,而傳入的參數FilterConfig實際上就是對應web.xml中的Filter配置信息,doFilter方法是一個Filter功能實現的核心。Filter處理StrutsPrepareFilter的init方法主要是初始化dispatcher,這是由於dispatcher是一個非常重要的類,但是由於如果詳細解釋dispatcher比較麻煩,大家有興趣可以自行了解。
這里主要分析doFilter方法,59行處調用了工具類PrepareOperations的createActionContext創建一個action的上下文。
由於是第一次,所以會進入到else分支中,74行處通過工廠模式創建了一個ValueStack(值棧),大家到這里debug一下就知道這里創建的實例實際上是一個OgnlValueStack對象,而它就是Struts2的ognl表達式注入的元凶(后面我們分析漏洞的時候再進入該類中看下源碼)。隨后又將request對象、response對象以及servlet上下文放入了OgnlValueStack中的Context屬性(一個map),並用該Context又創建了action上下文並返回。
回到StrutsPrepareFilter的doFilter方法中,assignDispatcherToThread將當前dispatcher放入到當前本地線程中,setEncodingAndLocale用於設置請求的本地化,語言以及編碼格式,不用管。需要注意的是wrapRequest方法,該方法根據請求類型的不同,采用不同的request包裝類,當為'multipart/form-data'時代表文件下載,將使用JakartaMultiPartRequest類,此類與S2-045有關,我們之后的文章再詳細分析。findActionMapping方法用於構建action的映射類,最后調用do.Filter進入到下一個過濾器(這里是StrutsExecuteFilter)。
更進findActionMapping函數:
同樣的,大家在184行處debug一下就知道了這里調用的是DefaultActionMapper的getMpping方法,跟進:
該方法內就是通過uri來解析對應的action配置信息,例如namespace、actionname、method。parseNameAndNamespace方法根據uri和配置文件中的namespace進行對比來判斷namespace,S2-057漏洞就和該函數相關(后面的文章分析)。handleSpecialParameters是struts2識別請求url后的特殊參數然后做一些特殊做處理,S2-016、S2-017、S2-018都與此函數相關。會識別如下四種特殊字符:
parseActionName方法用於,當項目開啟了動態方法調用時,也就是struts.xml配置了常量<constant name="struts.enable.DynamicMethodInvocation" value=
"true" />時,識別 !后面字符串作為action的method。
到這里StrutsPrepareFilter就分析完了,主要工作有兩點:一是為struts2執行做一些相關的准備,如加載相關的配置信息。二是為struts2的request請求處理相關的信息。
接下來到了StrutsExecuteFilter,該攔截器才是真正執行action請求的,進入doFilter方法:
跟進executeAction發現實際上是調用了Dispatcher.serviceAction,這里直接跟進serviceAction就好了:
值得注意的是如果此時的mapping中已經有了Result說明該請求時直接訪問的頁面,將會進入if分支,執行StrutsResultSupport.execute方法,關於這個方法,我們后面會講到。
StrutsActionProxy的execute方法中實際上又是通過調用了DefaultActionInvocation的invoke來執行action的
也就是說Dispatcher類是重要的調結者,而DefaultActionInvocation類才是執行action類實例的行動者。action代理類(ActionProxy類)則是他們之間的中間人。相當於Dispatcher類通過action代理類(ActionProxy類)命令DefaultActionInvocation類去執行action類實例。跟進DefaultActionInvocation的invoke方法:
struts2自動的默認攔截器中也存在很多問題,不僅只有S2-019,還有S2-020(ParametersInterceptor)、S2-021(ParametersInterceptor)、S2-022(CookieInterceptor)
跟進invokeActionOnly中又通過調用了invokeAction,跟進invokeAction:
大家可以看到invokeAction中才是真正執行action的地方,不過由於版本的不同,高版本中該函數內並不是通過反射機制而是ognl表達式來的執行的action。執行完action中的方法后將會返回一個Result對象。Result對象就是將mvc模式中controller層和view層連接起來的地方。
回到DefaultActionInvocation的invoke方法中,執行完action后就是操作返回的result了。
跟進executeResult方法:
createResult()就是我們之前說的,執行完action后如果返回的是字符串,需要去配置文件(struts.xml)中找對應的Result類型,如果沒有設置type默認是dispatcher,對應着的Result類是org.apache.struts2.result.ServletDispatcherResult。所有的Result類都會繼承StrutsResultSupport類,這里execute方法時父類StrutsResultSupport的(前面提到過這個),跟進:
1.conditionalParse用於處理房前的location,也就是跳轉地址,里面會判斷location是否有ognl表達式,有的話將會執行表達式,也是因為可能是動態返回結果。我們后面分析漏洞時后詳細介紹該函數。
2.接着就是調用StrutsResultSupport子類的doExecute了。
Struts2自帶多個Result類型,在struts-default.xml中可以看到
strut2的部分漏洞也是和他們相關的,比如說S2-031就和xslt這個返回類型有關。
這里我們只進入ServletDispatcherResult類中的doExecute看看
里面的內容比較簡單,大概就是通過請求轉發到下一個servlet(jsp本身就是一個servlet)。到這里struts2的基本工作流程就分析完了
https://www.cnblogs.com/hayasi/category/869760.html
https://cwiki.apache.org/confluence/display/WW/Security+Bulletins