攻擊JavaWeb應用[5]-MVC安全

wyzsk發表於2020-08-19
作者: 園長 · 2013/07/25 13:31

注:這一節主要是消除很多人把JSP當作了JavaWeb的全部的誤解,瞭解MVC及其框架思想。MVC是用於組織程式碼用一種業務邏輯和資料顯示分離的方法,不管是Java的Struts2、SpringMVC還是PHP的ThinkPHP都爆出過高危的任意程式碼執行,本節重在讓更多的人瞭解MVC和MVC框架安全,由淺到深儘可能的照顧沒Java基礎的朋友。所謂攻擊JavaWeb,如果連JavaWeb是個什麼,有什麼特性都不知道,就算能用Struts刷再多的RANK又有何意義?還不如沏一杯清茶,讀一本好書,不浮躁,擼上一天。

0x00、初識MVC


傳統的開發存在結構混亂易用性差耦合度高可維護性差等多種問題,為了解決這些毛病分層思想和MVC框架就出現了。MVC是三個單詞的縮寫,分別為: 模型(Model),檢視(View) 和控制(Controller)。 MVC模式的目的就是實現Web系統的職能分工。

Model層實現系統中的業務邏輯,通常可以用JavaBean或EJB來實現。

View*層用於與使用者的互動,通常用JSP來實現(前面有講到,JavaWeb專案中如果不採用JSP作為展現層完全可以沒有任何JSP檔案,甚至是過濾一切JSP請求,JEECMS是一個最為典型的案例)。

Controller層Model與View之間溝通的橋樑,它可以分派使用者的請求並選擇恰當的檢視用於顯示,同時它也可以解釋使用者的輸入並將它們對映為模型層可執行的操作。

2013072423121653672.jpg

Model1和Model2:

Model1主要是用JSP去處理來自客戶端的請求,所有的業務邏輯都在一個或者多個JSP頁面裡面完成,這種是最不科學的。舉例:http://localhost/show_user.jsp?id=2。JSP頁面獲取到引數id=2就會帶到資料庫去查詢資料庫當中id等於 2的使用者資料,由於這樣的實現方式雖然簡單,但是維護成本就非常高。JSP頁面跟邏輯業務都捆綁在一起高耦合了。而軟體開發的目標就是為了去解耦,讓程式之間的依賴性減小。在model1裡面SQL隱碼攻擊等攻擊簡直就是家常便飯。因為在頁面裡面頻繁的去處理各種業務會非常麻煩,更別說關注安全了。典型的Model1的程式碼就是之前用於演示的SQL隱碼攻擊的JSP頁面。

Model1的流程:

2013072423125077387.png

Model 2表示的是基於MVC模式的框架,JSP+Servlet。Model2已經帶有一定的分層思想了,即Jsp只做簡單的展現層,Servlet做後端的業務邏輯處理。這樣檢視和業務邏輯就相應的分開了。例如:http://localhost/ShowUserServlet?id=2。也就是說把請求交給Servlet處理,Servlet處理完成後再交給jsp或HTML做頁面展示。JSP頁面就不必要去關心你傳入的id=2是怎麼查詢出來的,而是怎麼樣去顯示id=2的使用者的資訊(多是用EL表示式或JSP指令碼做頁面展現)。檢視和邏輯分開的好處是可以更加清晰的去處理業務邏輯,這樣的出現安全問題的機率會相對降低。

2013072423133120903.png

Mvc框架存在的問題:

當Model1和Model2都難以滿足開發需求的時候,通用性的MVC框架也就產生了,模型檢視控制器,各司其責程式結構一目瞭然,業務安全相關控制井井有序,這便是MVC框架給我們帶來的好處,但是不幸的是由於MVC的框架的實現各自不同,某些東西因為其越來越強大,而衍生出來越來越多的安全問題,典型的由於安全問題處理不當造成近期無數網際網路站被黑闊攻擊的MVC框架便是Struts2。神器過於鋒利傷到自己也就在所難免了。而在Struts和Spring當中最喜歡被人用來挖0day的就是標籤和OGNL的安全處理問題了。

Spring Mvc:

Spring 框架提供了構建 Web應用程式的全功能 MVC 模組。使用 Spring 可插入的 MVC 架構,可以選擇是使用內建的 Spring Web 框架還是 Struts 這樣的 Web 框架。透過策略介面,Spring 框架是高度可配置的,而且包含多種檢視技術,例如 JavaServer Pages(JSP)技術、Velocity、Tiles、iText 和 POI、Freemarker。Spring MVC 框架並不知道使用的檢視,所以不會強迫您只使用 JSP 技術。Spring MVC 分離了控制器、模型物件、分派器以及處理程式物件的角色,這種分離讓它們更容易進行定製。

Struts2:

Struts是apache基金會jakarta專案組的一個開源專案,採用MVC模式,能夠很好的幫助我們提高開發web專案的效率。Struts主要採用了servlet和jsp技術來實現,把servlet、jsp、標籤庫等技術整合到整個框架中。Struts2比Struts1內部實現更加複雜,但是使用起來更加簡單,功能更加強大。

Struts2歷史版本下載地址:http://archive.apache.org/dist/struts/binaries/

官方網站是: http://struts.apache.org/

常見MVC比較:

按效能排序: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的Servlet和Filter:

可以說JavaWeb和PHP的實現有著本質的區別,PHP屬於解釋性語言.不需要在伺服器啟動的時候就透過一堆的配置去初始化apps而是在任意一個請求到達以後再去載入配置完成來自客戶端的請求。ASP和PHP有個非常大的共同點就是不需要預先編譯成類似Java的位元組碼檔案,所有的類方法都存在於*.PHP檔案當中。而在Java裡面可以在專案啟動時去載入配置到Servlet容器內。在web.xml裡面配置一個Servlet或者Filter後可以非常輕鬆的攔截、過濾來自於客戶端的任意字尾請求。在系列2的時候就有提到Servlet,這裡再重溫一下。

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>
    <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過濾:

2013072423184674687.png

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啟動和初始化的,下面會講到:

2013072423193885093.png

0x01、Struts概述


Struts1、Struts2、Webwork關係:

Struts1是第一個廣泛流行的mvc框架,使用及其廣泛。但是,隨著技術的發展,尤其是JSF、ajax等技術的興起,Struts1有點跟不上時代的步伐,以及他自己在設計上的一些硬傷,阻礙了他的發展。

同時,大量新的mvc框架漸漸大踏步發展,尤其是webwork。Webwork是opensymphony組織開發的。Webwork實現了更加優美的設計,更加強大而易用的功能。

後來,struts和webwork兩大社群決定合併兩個專案,完成struts2.事實上,struts2是以webwork為核心開發的,更加類似於webwork框架,跟struts1相差甚遠

STRUTS2框架內部流程:

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返回給伺服器。

SpringMVC框架內部流程

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架構更加清晰的瞭解。

Struts2請求處理流程分析:

2013072423210282176.png

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

2013072423291864880.png

原始碼、配置和訪問截圖:

2013072423293638349.png

0x02、Struts2中ActionContext、ValueStack、Ognl


在學習Struts命令執行之前必須得知道什麼是OGNL、ActionContext、ValueStack。在前面已經強調過很多次容器的概念了。這地方不敢再扯遠了,不然就再也扯回不來了。大概理解:tomcat之類的是個大箱子,裡面裝了很多小箱子,小箱子裡面裝了很多小東西。而Struts2其實就是在把很多東西進行包裝,要取小東西的時候直接從struts2包裝好的箱子裡面去拿就行了。

ActionContext物件:

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”屬性。內容如下:

2013072423331472319.png

OgnlValueStack可以簡單看做List,裡面還放了Action物件的引用,透過它可以得到該Action物件的引用。

下圖說明了幾個物件的關係:

2013072423333795056.jpg

1.  ActionContext、Action本身和HttpServletRequest物件沒有關係。但是為了能夠使用EL表示式、JSTL直接操作他們的屬性。會有一個攔截器將ActionContext、Action中的屬性透過類似request.setAttribute()方法置入request中(webwork2.1之前的做法)。這樣,我們也可以透過:${requestScope.uname}即可訪問到ActionContext和Action中的屬性。

注:struts2後,使用裝飾器模式來實現上述功能。

Action的例項,總是放到value stack中。因為Action放在stack中,而stack是root(根物件),所以對Action中的屬性的訪問就可以省略#標記。

獲取Web容器資訊:

在上面我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*();

Struts2 OGNL:

OGNL全稱是Object-Graph  Navigation  Language(物件圖形導航語言),Ognl同時也是Struts2預設的表示式語言。每一次Struts2的命令執行漏洞都是透過OGNL去執行的。在寫這文件之前,烏雲的drops已有可夠參考的Ognl文章了/papers/?id=340。這裡只是簡單提下。

1、能夠訪問物件的普通方法
2、能夠訪問類的靜態屬性和靜態方法
3、強大的操作集合類物件的能力
4、支援賦值操作和表示式串聯
5、訪問OGNL上下文和ActionContext

Ognl並不是Struts專用,我們一樣可以在普通的類裡面一樣可以使用Ognl,比如用Ognl去訪問一個普通物件中的屬性:

2013072423341292194.png

在上面已經列舉出了Ognl可以呼叫靜態方法,比如表示式使用表示式去呼叫runtime執行命令執行:

@[email protected]().exec('net user selina 123 /add')

而在Java當中靜態呼叫命令列的方式:

java.lang.Runtime.*getRuntime*().exec("net user selina 123 /add");

2013072423342841300.png

0x03、Struts漏洞


Struts2究竟是個什麼玩意,漏洞爆得跟來大姨媽紊亂似的,連續不斷。前面已經提到了由於Struts2預設使用的是OGNL表示式,而OGNL表示式有著訪問物件的普通方法和靜態方法的能力。開發者無視安全問題大量的使用Ognl表示式這正是導致Struts2漏洞源源不斷的根本原因。透過上面的DEMO應該差不多知道了Ognl執行方式,而Struts2的每一個命令執行後面都堅挺著一個或多個可以繞過補丁或是直接構造了一個可執行的Ognl表示式語句。

Struts2漏洞病例:

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/

Struts公開的安全問題:

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

相關文章