下載地址:https://codeload.github.com/apache/shiro/zip/shiro-root-1.2.4java
環境:Tomcat 8.5.27 + idea 2020.2 + jdk 1.8 +maven 3.6git
下載以後以後直接打開,並open這個web文件夾便可,其餘自行百度就行,其中還須要導入一些jstl的jar等等github
shiro默認使用了CookieRememberMeManager
,其處理cookie的流程是:web
獲得rememberMe的cookie值 --> Base64解碼 --> AES解密 --> 反序列化
然而AES的密鑰是硬編碼的,就致使了攻擊者能夠構造惡意數據形成反序列化的RCE漏洞。apache
payload 構造的順序則就是相對的反着來:數組
惡意命令-->序列化-->AES加密-->base64編碼-->發送cookie
在整個漏洞利用過程當中,比較重要的是AES加密的密鑰,該祕鑰默認是默認硬編碼的,因此若是沒有修改默認的密鑰,就本身能夠生成惡意構造的cookie了。cookie
shiro特徵:session
復現文章https://blog.csdn.net/weixin_43571641/article/details/108182722框架
簡單介紹利用:maven
那既然咱們要分析,那入口點在哪呢?Shiro≤1.2.4版本默認使用CookieRememberMeManager
而咱們看看這邊CookieRememberMeManager
類繼承了AbstractRememberMeManager
,咱們進去看看是什麼梗
咱們能夠看到這邊這個類裏面有硬編碼。而後它又繼承了RememberMeManager
接口;咱們繼續進去看看是怎麼回事
看名字的話能夠知道這些是登錄成功,登錄失敗,退出的一些service;既然如此,確定會調用這個登錄成功的接口,而後再去實現這個接口。因此咱們直接在這個接口下個斷點,看看是怎麼個流程;
這裏看到調用了isRememberMe()
能夠發現這個就是一個判斷用戶是否選擇了RememberMe
選項。而咱們是勾選了的
因此咱們咱們條件知足,這邊判斷返回True,咱們則進入this.rememberIdentity(subject, token, info);
subject
存儲的一些登錄信息如session等等,而authcInfo
存儲的則是用戶名;
而PrincipalCollection是一個身份集合,由於咱們能夠在Shiro中同時配置多個Realm,因此呢身份信息可能就有多個;所以其提供了PrincipalCollection用於聚合這些身份信息,具體咱們不細講,不深刻去懂原理。
而後咱們再F7繼續跟進this.rememberIdentity(subject, principals);
這咱們有點懵,將身份信息幹嗎?咱們進入該convertPrincipalsToBytes()
方法查看;
看到了serialize()
方法,難道這邊開始是進行序列化了仍是啥?
經過此處咱們能夠知道是跳了兩層,到DefaultSerializer
類的serialize
方法;看到這裏就懂了,這裏先轉爲byte,寫入緩衝區;而後進行了一個序列化,最後經過toByteArray()
方法返回序列化後的Byte數組。
而後返回到原來的地方convertPrincipalsToBytes()
內,接下來if判斷getCipherService()
方法不爲空,則進入條件裏面裏面。咱們f7進去內部看看;
發現又是一個cipherService
,這是什麼;咱們翻譯一下,由於大部分開發都會用簡稱;
也就是獲取密碼服務?? 什麼密碼服務?咱們再繼續F7跟進發現直接推出了。那咱們就 Ctrl+左鍵
繼續進去看。能夠,發現是new了一個aes加密服務。
那咱們點擊debugger處,回到剛剛那個地方;咱們就不用繼續進入了,咱們就思考一下,這邊是要獲取到加密服務,若是沒獲取到,則不進入。獲取到的話,則進入該條件;
直接F8下來,進入,而後咱們再手動添加變量監視。能夠發現正如咱們所想的,獲取aes加密服務;
而後調用encrypt()
方法,而懂點英文的,都知道這個單詞是加密的意思。那咱們初步判斷這是個加密方法。咱們f7跟進去看看什麼狀況。
咱們能夠知道這個參數是byte[] serialized
,也就是說,此處加密咱們剛剛的序列化流的數據。
而後這邊this.getCipherService()
咱們剛剛手動添加變量查看了,這邊是獲取到了aes加密服務;而後判斷不問空,那確定不爲空啊,剛剛上面分析過了。而後咱們進入條件判斷股內部。
ByteSource byteSource = cipherService.encrypt(serialized, this.getEncryptionCipherKey());
這裏調用cipherService.encrypt()
方法而且傳入序列化數據,和getEncryptionCipherKey
方法。加密過程,咱們就應該不怎麼感興趣了;有興趣的能夠本身研究
咱們經過getEncryptionCipherKey()
名字能夠知道是獲取key的一個方法。那咱們f7進入看看
哦豁,那咱們再進一層看一下;發現直接就返回了,emmmmm....怎麼跟別人不同。那咱們就不追了
第一步有說到,硬編碼存儲在這個地方,而構造方法就在這下面,能夠看到這邊設置了key。
咱們繼續回到原來的地方,知道這邊是獲取加密的key就ok了。而後這邊使用平臺的默認字符集將字符串編碼爲 byte 序列,並將結果存儲到一個新的 byte 數組中。那咱們加密部分就結束了
因爲此處,我找不到,回溯不到,那咋辦,煩惱;最後想到了咱們加密的入口~~
既然自動跳到了這裏,那麼咱們就直接在此處下個斷點,從新開始
隨後咱們進入這個getRememberedSerializedIdentity()
方法,看看是什麼東西。此處咱們依然還很懵,沒事;
一直f8,期間卻是沒有什麼有意思或者重點的地方;
直到咱們走到這裏,這個有一個this.getCookie().readValue(request, response)
,這是要讀取cookice中的數據了,這必須跟入了;
這裏給進到了這個readvalue()
方法中了,咱們先看看什麼狀況。根據名字能夠知道是讀取值的一個方法。讀取什麼值?請求包的值。
經過getName()
方法獲得了key爲remeberMe。而後把value置空,再經過getCookie
獲取到cookie。最後判斷cookie不爲空,則進入內部;隨後獲取到cookie的值;值則爲序列化內容。而後再 return回序列化內容;
隨後返回到上一處地方如今remeberMe
的值不是delete
;而是序列化內容,因此進入到第二個條件分支。
一直到這一步,進行base64解碼,成爲二進制數據,給了decoded的byte數組;
獲得rememberMe的cookie值 --> Base64解碼 --> AES解密 --> 反序列化
目前只進行了Base64解碼,那還須要aes解碼。咱們繼續跟進
返回到了上層,此處咱們知道bytes是二進制數據,咱們看看條件判斷。當bytes數組不爲空且長度大於0時,進入裏面。那咱們確定知足,因此咱們兩步f8加一步F7進入到 convertBytesToPrincipals
看看是什麼
能夠看出咱們接下來的步驟要依依實現了。判斷key不爲空,而後進入內部
而從這裏開始,就是進行aes解密的步驟了,咱們F7跟進方法查看
這裏從新把惡意的bytes數組從新賦值給serialized
,而後再獲取加密服務:AES/CBC/PKCS5Padding
同時到達了下一步;真真正正的開始解密了,其中兩個參數,第一個是加密的bytes數組,第二個是獲取到key,也就是硬編碼;咱們 就直接進入decrypt()
方法中
解密過程的話,我不擅長密碼學,這種看着我頭暈,涉及到aes啥的加密解密我就會跳過。因此依舊同樣,跳!!!
此處繼續返回到了上一層,咱們能夠看出這個byteSource是aes解密出來的序列化流,而後再默認字符集將字符串編碼爲 byte 序列,並將結果存儲到一個新的 byte 數組serialized
中,那接下來咱們就差反序列化了
獲得rememberMe的cookie值 --> Base64解碼 --> AES解密 --> 反序列化
咱們繼續return,返回到上一層
顧名思義,一看名字就知道是反序列化的方法,咱們跟進deserialize()
方法查看
看到還有一層,咱們繼續F7跟進
造成反序列化漏洞的話,沒有readObject()
怎麼可能呢?因此咱們看到了最後一道光,就這麼愉快的結束了。
其實這個仍是得學習學習加密解密的方法,才能進行編寫poc,可是此處只是瞭解個思路。具體可參考其餘文章;我空餘時間的話,會繼續補全該篇文章,先去搞其餘的玩意兒
[P牛博客](https://zeo.cool/2020/09/03/Shiro 550 反序列化漏洞 詳細分析+poc編寫/#解密過程:)