先感謝一下scz大佬給的博客推薦,受寵若驚+10086。
心血來潮有這麼個想法,驗證一下。
只討論思路,工具木有~
前言
-
看了很多師傅們在研究針對Java中間件+Java反序列化漏洞回顯的研究:
- https://xz.aliyun.com/t/7740
- https://xz.aliyun.com/t/7348
- https://paper.seebug.org/1233/
- (。。。應該還有挺多,就不翻了)
-
又看到了c0ny1師傅的作品:《java內存對象搜索輔助工具》
配合IDEA在Java應用運行時,對內存中的對象進行搜索。比如可以可以用挖掘request對象用於回顯等場景。
…
按照經驗來講Web中間件是多線程的應用,一般requst對象都會存儲在線程對象中,可以通過
Thread.currentThread()
或Thread.getThreads()
獲取。 -
並且目前回顯思路主要是基於加載類,執行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
- java類中間件的request和response分別實現以上兩個接口
- 從Thread.currentThread()起始搜索實現瞭如上兩個接口的對象
- 通過HttpServletRequest的getHeader方法可以獲取到請求頭
- 通過HTTPServletResponse的getWriter方法可以獲取到響應的Writer(開始用的ServletResponse的getOutputStream接口,但是會報重複獲取的OutputStream的錯誤,此處就改爲使用getWriter了)
- 都搜索到後,該執行執行,該輸出輸出
初始代碼
有點多,都貼上有水字數的嫌疑,還是貼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字節
- 去掉字符串連接:
3640字節
-
刪除多餘邏輯的限制邏輯
if(depth > 50||(req!=null&&resp!=null)){ return; }
3611字節
-
刪除getModifiers的調用(刪除一個方法大概60字節的樣子?)
3551字節
減少了一個空判斷,大概4字節
3547
-
減少局變量的定義
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字節
-
去掉調試時用的printStackTrace
3488字節
-
刪掉flush和waitFor
3451字節,貌似沒少多少
-
刪掉static塊和start方法,只保留構造方法觸發
3372
-
刪除PrintWriter的變量賦值,刪除Test by fnmsd字符串
3260
-
刪除掉proc的定義,直接跟getInputStream一塊調用
3220
-
合併Scanner相關邏輯爲一句:
resp.getWriter().println(new Scanner(Runtime.getRuntime().exec(req.getHeader("cmd")).getInputStream()).useDelimiter("\\A").next())
3130
以下本應有判斷來進行的剪枝,改爲使用異常處理兜着
-
去掉java.lang類的搜索剪枝
obj.getClass().toString().startsWith("java.lang")
節省了toString和startsWith
3009
-
去掉數組包裹類型的爲非primtive的判斷
obj.getClass().getComponentType().isPrimitive()
2962
-
去掉全部isPrimitive的判斷
2917
-
去掉搜索時字段值爲null的判斷
2908
-
給類名、字段名、方法名、局部變量名都改爲一個字符
2850
這裏改局部變量其實不起作用。
-
去掉是否爲靜態字段的判斷
if((declaredField.getModifiers()&0x00000008) == 0)
2803(最終大小在2800-2900之間,不會差太多,後面不知道又改哪裏了,到了2900左右)
瘦身總結
最終結果代碼(使用構造方法觸發,也可以改成Static,都一樣):https://gist.github.com/fnmsd/8165cedd9fe735d7ef438b2e977af327
(前面傳錯了一箇中間的bug版本,會重複執行重複回顯,感謝l1nk3r師傅早早的幫我發現了)
- 儘量減少引用的方法和類型(包括像字符串連接這種隱式調用)
- 儘量減少字段、局部變量的定義
- 能用異常處理兜底的處理,減少判斷。
- 蚊子腿也是肉,把字段名、參數名、變量名改小點(這塊不確定,可能只有字段名有效)
- 比較大的地方主要在ConstantPool和變量定義上(ConstantPool還好理解,變量定義不太明白,後續還是好好在學習學習Class文件結構)
測試
-
由於都是new一個對象或者static塊執行,所以偷了個懶,簡單寫了個jsp頁面,可以看到沒有任何的輸出語句,只是new了一下對象:
<%@ page import="aa.a" %> <%@ page contentType="text/html;charset=UTF-8" language="java" %> <% new a(); %>
Weblogic(打包有點問題,頁面沒改成最簡版的):
Tomcat:
Jetty:
-
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
其他
- 原來設置了深度優先搜索最大50層,結果weblogic下,request搜索出來在第50層,response在request下面第51層,所以後來我最大深度改到了52層。
- 具體使用中使用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