shiro550反序列學習

Shiro550

shiro550和fastjson作為攻防演練的利器,前面學習了fastjson的相關利用和回顯,本篇主要來學習一下shiro550的漏洞原理。

1、漏洞原因

在 Shiro <= 1.2.4 中,AES 加密演算法的key是硬編碼在源碼中,當我們勾選remember me 的時候 shiro 會將我們的 cookie 資訊序列化並且加密存儲在 Cookie 的 rememberMe欄位中,這樣在下次請求時會讀取 Cookie 中的 rememberMe欄位並且進行解密然後反序列化,且AES的key 為固定的。

2、環境搭建及復現

//codeload.github.com/apache/shiro/zip/shiro-root-1.2.4
jdk1.7
Tomcat8
idea

image-20220421195258234

坑點

原有版本的jstl會報錯修改為1.2

<dependency>
  <groupId>javax.servlet</groupId>
  <artifactId>jstl</artifactId>
  <version>1.2</version>
</dependency>
<!-- 依賴cc鏈 -->
<dependency>
  <groupId>org.apache.commons</groupId>
  <artifactId>commons-collections4</artifactId>
  <version>4.0</version>
</dependency>

toolchains的錯誤

<?xml version="1.0" encoding="UTF8"?>
<toolchains>
    <toolchain>
        <type>jdk</type>
        <provides>
            <version>1.6</version>
            <vendor>sun</vendor>
        </provides>
        <configuration>
            <jdkHome>/Library/Java/JavaVirtualMachines/jdk1.8.0_161.jdk</jdkHome>
        </configuration>
    </toolchain>
</toolchains>

image-20220421195435370

然後啟動即可

image-20220421195712873

漏洞復現

image-20220421203614317

3、漏洞分析

3.1、生成cookie

3.1.1、原理解析

shiro會提供rememberme功能,可以通過cookie記錄登錄用戶,從而記錄登錄用戶的身份認證資訊,即下次無需登錄即可訪問。而其中對rememberme的cookie做了加密處理,漏洞主要原因是加密的AES密鑰是硬編碼在文件中的,那麼對於AES加密演算法我們已知密鑰,並且IV為cookie進行base64解碼後的前16個位元組,因此我們可以構造任意的可控序列化payload。

cookie的處理類org.apache.shiro.web.mgt.CookieRememberMeManager類出現了漏洞,而它繼承了AbstractRememberMeManager類。

image-20220426230340573

處理rememberme的cookie的類為org.apache.shiro.web.mgt.CookieRememberMeManager,其中的rememberSerializedIdentity方法,主要就是設置用戶的cookie的值,這個值是通過base64解密serialized獲取的。

image-20220427000507263

我們繼續看看父類

首先定義默認的秘鑰通過base64固定解碼得到,這個就是我們上門工具爆破出來的秘鑰

image-20220426230830597

還有就是方法 onSuccessfulLogin,這方法就是登錄成功的意思,判斷isRememberMe,該方法就是判斷token中是夠含rememberMe。所以當我們成功登錄時,如果勾選了rememberme選項,那麼此時將進入onSuccessfulLogin方法

image-20220426231549007

我們繼續往下走,跟進rememberIdentity方法,這三個參數上面有解釋我,我的理解是

subject:就是rememberMe欄位的主體
token:成功的身份令牌
authcInfo:成功的身份驗證資訊

然後進入方法體,獲取驗證身份的主體,繼續調用重載方法,再跟進去看看

image-20220426233426758

進入後我看到,把accountPrincipals(身份驗證資訊)轉成了byte位元組,然後就是調用我們一開始分析的rememberSerializedIdentity方法設置cookie的值了。

image-20220426234224799

這就是生成cookie的過程,但是還是有些疑惑,convertPrincipalsToBytes是怎麼實現的和默認秘鑰在哪裡使用了,我們在org.apache.shiro.mgt.AbstractRememberMeManager的onSuccessfulLogin方法打下斷點看看。

3.1.1、idea調試

首先我們idea調試啟動,然後勾選Rememberme,登錄。

image-20220427003515798

成功捕獲斷點,跟進去

image-20220427003842893

跟我們上面分析的一樣,我們直接跟進convertPrincipalsToBytes方法

image-20220427003909890

image-20220427003940621

我們先跟進serialize,看他怎麼序列化數據的

image-20220427004019394

獲取序列化對象繼續調用serialize,跟進去

image-20220427004121665

看到這我們就能很清晰的看到他是怎麼序列化數據的了。我們繼續回到convertPrincipalsToBytes方法

image-20220427004247818

接著判斷getCipherService是否為空,字面意思就是獲取密碼演算法服務,我們也跟進去看看

image-20220427004348174

直接返回了加密演算法服務,在註解中也可以看到,為CBC模式的AES演算法。那他在哪裡定義的呢

image-20220427004535893

我們看到在構造方法中,創建了AES加密服務,並且設置了加密服務的key,這個key就是我們上面定義的。

image-20220427004716112

我們繼續返回convertPrincipalsToBytes方法中。看到其使用了encrypt方法對序列化後的principals進行加密,我們也跟進去看看。

image-20220427005003365

首先還是獲取了加密演算法服務(AES),調用該演算法的加密方法encrypt,這個演算法有兩個參數,第一個我們知道就是序列化的位元組碼,我們看第二個,英文意思是獲取加密演算法的key,我們繼續跟進去

image-20220427005209855

我們看到直接返回了加密key,這個key是通過setEncryptionCipherKey設置的,而setCipherKey調用了setEncryptionCipherKey,也就是我們在encrypt方法中的getCipherService方法設置的.

image-20220427005518532

image-20220427005647226

image-20220427005727405

我們繼續回到encrypt方法,參數是 bytes和key繼續跟進

image-20220427010103797

判斷是夠創建初始化載體,我們跟進generateInitializationVector

image-20220427010356236

他會調用父類的generateInitializationVector,繼續跟進

image-20220427010634344

我們可以看到,size為128(定義的靜態字元串),然後新建長度為16的位元組數組,調用了ensureSecureRandom,跟進看看

image-20220427011832217

image-20220427011133512

就是獲取一個隨機值,nextBytes方法用於生成隨機位元組並將其置於用戶提供的位元組數組

image-20220427011307560

然後返回,所以ivBytes就是一個隨機的16位位元組數組

image-20220427011801912

我們繼續回到encrypt,然後調用重載方法,參數為byte數組、key,16為的隨機位元組數組ivBytes和布爾true。我們繼續跟進

image-20220427012111715

encrypt就是我們最終的加密實現演算法。把ivbytes和encrypted一起放入output,然後返回

image-20220427012422800

最後通過rememberSerializedIdentity設置cookie值

image-20220427012737973

image-20220427012856831

上面都是序列的過程,那麼在那裡反序列化呢

3.3、解析cookie

org.apache.shiro.mgt.AbstractRememberMeManager#getRememberedPrincipals的方法中,會從cookie中獲取身份資訊,我們在此打下斷點,然後刷新web頁面,成功捕獲,會通過org.apache.shiro.web.mgt.CookieRememberMeManager類的getRememberedSerializedIdentity方法獲取bytes,我們也跟進去看看

image-20220427014215067

可以看到該類會獲取cookie,然後解密base64加密的欄位,獲取位元組數組返回。

image-20220427014456605

我們繼續返回其父類org.apache.shiro.mgt.AbstractRememberMeManager#getRememberedPrincipals的方法中,會調用convertBytesToPrincipals方法,跟生成cookie相反,我們也跟進去看看。

image-20220427014715380

重複程式碼不再一一解釋,直接進入decrypt方法

image-20220427014815115

發現跟加密一樣,通過decrypt解密AES。然後返回

image-20220427014906425

然後反序列化解密的字元串

image-20220427015048159

image-20220427015119267

最後調用readObject方法,造成反序列化。

image-20220427015211210

值得注意的是該readObejct方法,是shiro重寫過的,重寫了resolveClass函數,調用了ClassUtils.forName(),而原生的則是Class.forName().所以導致很多鏈用不了 ,也是為什麼要導入cc4的組件了。我們來看看ClassUtils.forName()

image-20220427015359185

看到org.apache.shiro.util.ClassUtils的forName()方法,他是調用了而ClassLoader.loadClass,該方法不支援裝載數組類型的class,也就是cc1、cc3等用的Transform數組類都不行了,但是cc2和cc4是可以的 ,其利用的是javassist的TemplateImpl類實現的,所以不影響。

image-20220427015816256

還有就是通過改造利用鏈實現shiro原生的命令執行,具體查看//www.anquanke.com/post/id/192619#h2-3。

參考

//y4er.com/post/shiro-rememberme-rce/

//www.cnblogs.com/nice0e3/p/14183173.html

//xz.aliyun.com/t/6493

//www.anquanke.com/post/id/192619#h2-3