基於請求/響應對象搜索的Java中間件通用回顯方法(針對HTTP)

先感謝一下scz大佬給的博客推薦,受寵若驚+10086。

心血來潮有這麼個想法,驗證一下。

只討論思路,工具木有~

前言

  1. 看了很多師傅們在研究針對Java中間件+Java反序列化漏洞回顯的研究:

    • https://xz.aliyun.com/t/7740
    • https://xz.aliyun.com/t/7348
    • https://paper.seebug.org/1233/
    • (。。。應該還有挺多,就不翻了)
  2. 又看到了c0ny1師傅的作品:《java內存對象搜索輔助工具》

    配合IDEA在Java應用運行時,對內存中的對象進行搜索。比如可以可以用挖掘request對象用於回顯等場景。

    按照經驗來講Web中間件是多線程的應用,一般requst對象都會存儲在線程對象中,可以通過Thread.currentThread()Thread.getThreads()獲取。

  3. 並且目前回顯思路主要是基於加載類,執行static塊或者構造方法(原生反序列化、FastJson、Jackson一類的都有):

    • TemplatesImpl類的反序列化鏈,內嵌類的bytecode,defineClass。
    • 其他反序列化鏈使用URLClassLoader進行遠程加載類。
    • JNDI遠程加載類。

所以想到: 我們能否寫單個類,讓它能夠觸發時,使用2的思路去尋找request和response,從request中獲取命令參數,然後向Response中寫入呢?

先說結論: 可以實現,在Tomcat、Jetty和Weblogic中都可使用(只測了這三,個人覺得其它Java中間件也沒差),還測試了Shiro、Fastjson、Jackson反序列化的場景。

Tomcat6+Shiro會報java.io.StreamCorruptedException: invalid type code:錯誤,很迷,有空再解決吧。

精簡後的編譯完的class文件大小在2800-2900B左右,Shiro反序列化用的Cookie值大小可以控制在5000字節左右,勉強可以接受。

響應時間一般在3S內。

基本思路

javax.servlet.http.HttpServletRequest

javax.servlet.http.HTTPServletResponse

  1. java類中間件的request和response分別實現以上兩個接口
  2. 從Thread.currentThread()起始搜索實現瞭如上兩個接口的對象
  3. 通過HttpServletRequest的getHeader方法可以獲取到請求頭
  4. 通過HTTPServletResponse的getWriter方法可以獲取到響應的Writer(開始用的ServletResponse的getOutputStream接口,但是會報重複獲取的OutputStream的錯誤,此處就改爲使用getWriter了)
  5. 都搜索到後,該執行執行,該輸出輸出

初始代碼

有點多,都貼上有水字數的嫌疑,還是貼gist鏈接吧。┓( ´∀` )┏

https://gist.github.com/fnmsd/89118c2967cd53c244389564d2f8b368

編譯完的class 3888字節,好吉利~

  • 爲了類能小一些,沒有做c0ny1師傅那麼細緻的搜索分類和剪枝。

  • 爲了方便,純粹使用深度優先搜索(DFS),依次搜索字段

  • 然而接下來爲了瘦身,各種先定邏輯還得繼續砍砍砍

**PS:**在搜索Tomcat的Request過程中,不知道爲何會搜出一個並非當前Request的對象,所以這裏限制了Request必須包含cmd頭,才認爲找到了真的Request,Response目前沒有發現這個問題。

爲代碼瘦身

由於Nginx有Cookie 4096B長度的限制,Tomcat有8096B的長度限制(感謝c0ny1師傅),所以爲了能使用Templates類的鏈,編譯出的Class文件還是越小越好。

以下內容比較亂,是一個逐漸嘗試減小Class文件的過程,沒興趣可以略過

整個分析過程使用classpy-0.4.jar來進行對Class文件的分析:

https://github.com/zxh0/classpy

在這裏插入圖片描述

1.初始

​ 3888字節

2.去掉可有可無的靜態字段

    static Class ReqC = HttpServletRequest.class;
    static Class RespC = HttpServletResponse.class;       
    static int max_depth = 50;

​ 3734字節

  1. 去掉字符串連接:
    在這裏插入圖片描述
    在這裏插入圖片描述

3640字節

  1. 刪除多餘邏輯的限制邏輯

            if(depth > 50||(req!=null&&resp!=null)){
                return;
            }
    

    3611字節

  2. 刪除getModifiers的調用(刪除一個方法大概60字節的樣子?)

3551字節

減少了一個空判斷,大概4字節

3547

  1. 減少局變量的定義

    Class a = obj.getClass();
    if(a.isPrimitive()||a.toString().startsWith("java.lang")){
        return true;
    }
    

    變爲:

    if(obj.getClass().isPrimitive()||obj.getClass().toString().startsWith("java.lang")){
                return true;
            }
    

    3525,少了22字節

  2. 去掉調試時用的printStackTrace

    3488字節

  3. 刪掉flush和waitFor

    3451字節,貌似沒少多少

  4. 刪掉static塊和start方法,只保留構造方法觸發

    3372

  5. 刪除PrintWriter的變量賦值,刪除Test by fnmsd字符串

    3260

  6. 刪除掉proc的定義,直接跟getInputStream一塊調用

    3220

  7. 合併Scanner相關邏輯爲一句:

    resp.getWriter().println(new Scanner(Runtime.getRuntime().exec(req.getHeader("cmd")).getInputStream()).useDelimiter("\\A").next())

    3130

以下本應有判斷來進行的剪枝,改爲使用異常處理兜着

  1. 去掉java.lang類的搜索剪枝

    obj.getClass().toString().startsWith("java.lang")

    節省了toString和startsWith

    3009

  2. 去掉數組包裹類型的爲非primtive的判斷

    obj.getClass().getComponentType().isPrimitive()

    2962

  3. 去掉全部isPrimitive的判斷

    2917

  4. 去掉搜索時字段值爲null的判斷

    2908

  5. 給類名、字段名、方法名、局部變量名都改爲一個字符

    2850

    這裏改局部變量其實不起作用。

  6. 去掉是否爲靜態字段的判斷

    if((declaredField.getModifiers()&0x00000008) == 0)

    2803(最終大小在2800-2900之間,不會差太多,後面不知道又改哪裏了,到了2900左右)

瘦身總結

最終結果代碼(使用構造方法觸發,也可以改成Static,都一樣):https://gist.github.com/fnmsd/8165cedd9fe735d7ef438b2e977af327
(前面傳錯了一箇中間的bug版本,會重複執行重複回顯,感謝l1nk3r師傅早早的幫我發現了)

  1. 儘量減少引用的方法和類型(包括像字符串連接這種隱式調用)
  2. 儘量減少字段、局部變量的定義
  3. 能用異常處理兜底的處理,減少判斷。
  4. 蚊子腿也是肉,把字段名、參數名、變量名改小點(這塊不確定,可能只有字段名有效)
  5. 比較大的地方主要在ConstantPool和變量定義上(ConstantPool還好理解,變量定義不太明白,後續還是好好在學習學習Class文件結構)

測試

  1. 由於都是new一個對象或者static塊執行,所以偷了個懶,簡單寫了個jsp頁面,可以看到沒有任何的輸出語句,只是new了一下對象:

    <%@ page import="aa.a" %>
    <%@ page contentType="text/html;charset=UTF-8" language="java" %>
      <%
        new a();
      %>
    

    Weblogic(打包有點問題,頁面沒改成最簡版的):

在這裏插入圖片描述

Tomcat:
在這裏插入圖片描述
Jetty:
在這裏插入圖片描述

  1. Tomat8+Shiro反序列化

    這裏用的Jdk7u21的鏈(TemplatesImpl觸發)來new我寫的類,rememberMe長度5036

    懶得部環境的同學,可以用Vulhub裏面的Shiro環境,(SpringBoot用的嵌入式Tomcat9)但是那個得用CommonsBeanutils1的鏈。

在這裏插入圖片描述
3. Fastjson JNDI加載場景(這裏用vulhub的fastjson/1.2.47-rce,SpringBoot用的嵌入式Tomcat9)

在這裏插入圖片描述
4. weblogic12+Jackson反序列化

這個用的我之前給Jackson提的鏈,可惜沒混到CVE編號,嚶嚶嚶o(╥﹏╥)o
在這裏插入圖片描述

其他

  1. 原來設置了深度優先搜索最大50層,結果weblogic下,request搜索出來在第50層,response在request下面第51層,所以後來我最大深度改到了52層。
  2. 具體使用中使用TemplatesImpl需要類鏈,需要使用Javassist添加AbstractTranslet接口,當然,也可以直接implements,但是這樣又多倆需要實現的接口,class會大一些,具體可以參考:https://xz.aliyun.com/t/6227

一點思考: 非HTTP的反序列化,搜Socket對象,然後從getOutputStream再往裏寫能不能行呢?

參考資料

師傅們的研究:

https://xz.aliyun.com/t/7740

https://xz.aliyun.com/t/7348

https://paper.seebug.org/1233/

https://github.com/c0ny1/java-object-searcher

http://gv7.me/articles/2020/semi-automatic-mining-request-implements-multiple-middleware-echo/

J2EE的接口文檔:

https://docs.oracle.com/javaee/6/api/javax/servlet/http/HttpServletRequest.html

https://docs.oracle.com/javaee/6/api/javax/servlet/http/HttpServletResponse.html

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章