shiro550反序列化分析


拖了很久的shiro分析

漏洞概述

Apache Shiro <= 1.2.4 版本中,加密的用戶信息序列化后存儲在Cookie的rememberMe字段中,攻擊者可以使用Shiro的AES加密算法的默認密鑰來構造惡意的Cookie rememberMe值,發送到Shiro服務端之后會先后進行Base64解碼、AES解密、readObject()反序列化,從而觸發Java原生反序列化漏洞,進而實現RCE。

該漏洞的根源在於硬編碼Key。

漏洞復現

使用shiroattack工具

Dnslog接收到請求

執行命令

漏洞分析

遠程調試

用idea連vulhub的docker環境來進行調試
首先進入容器
docker exec -it 34db756dfcfc /bin/bash

可以看到是用jar包起的環境,把文件拷貝出來(也可以docker-compose up -d之后使用docker ps --no-trunc來查看容器默認的啟動命令)

docker cp 34db756dfcfc:/shirodemo-1.0-SNAPSHOT.jar ~/

然后把jar包解壓了之后用idea打開

libraries里導入

在module中添加BOOT_INF這個目錄

另外還需要改一下dockerfile
idea遠程調試docker
需要增加一組端口供調試用,這里我們用idea默認的5005

vulhub的shiro環境是java -jar xxx.jar的形式運行的,那么添加對jar程序啟動的調試命令即可,在啟動docker時用自定義的COMMAND替換默認的COMMAND

version: '2'
services:
 web:
   image: vulhub/shiro:1.2.4
   ports:
    - "8080:8080"
    - "5005:5005"
   command: java -agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=5005 -jar /shirodemo-1.0-SNAPSHOT.jar

然后配置好remote

下好斷點,輸入登錄賬戶密碼測試,看到以下界面說明成功了

原理分析

從官方的 issue 上來看,存在幾個重要的點:

  • rememberMe cookie
  • CookieRememberMeManager.java
  • Base64
  • AES
  • 加密密鑰硬編碼
  • Java serialization

首先正常登錄

返回的cookie值中的rememberme值如下:

o5NA+QAjgJpe6uKkIJ1li/WLqOAR2KfLIY3BzwfAUFbbkBSEfs3/259i4Qc2jq6lxUpLabYK2c0oR4faB3l8m0GDhzMvVYjFOR2TPKRtQmqlUAdaiJT06biAC56EMu6UbBJAujAgP1msHuJYkV1fDuhdLN5wGK+IlEpr1Xf+aa6oNkPqBkRG7+B/3bXQNTqmLFlarZUWxB6TwZyshplhx0ckyIfc0qJuf/f5Tt60gK/D28JUI93Gp3vGi/P7UUiwv2Qyzpz4hXZSUocGlC73qE+62ZvQ1ryzVRjpfTkG4Hat6sst04wbpFwdUSJxh6t4FLJ2i9bs5eIm/1UJpVHP9Eia5WaAShQa45qr5yIMA+q1rYxtz0WufvOi67fpqY3qi8LQ/ZnGwXUY+o6dLu2qmqHwTXbRTRKP4G5d3e5SA9FNvXUhYWRhcwo2zJ2lS2JK/D6S0u3HAak04+3wpbZm0UCJxafXlaFPUDhmiXBtIULcEELqBOaqLr1n7LV+F1Cl7kYNU6GozLVPvNlqW5UtLA==

跟一下登錄生成cookie的過程

生成cookie

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

處理rememberme的cookie的類為org.apache.shiro.web.mgt.CookieRememberMeManager

它繼承自org.apache.shiro.mgt.AbstractRememberMeManager,其中在AbstractRememberMeManager中定義了加密cookie所需要使用的密鑰,當我們成功登錄時,如果勾選了rememberme選項,那么此時將進入onSuccessfulLogin方法

之后進入serialize,對登錄認證信息進行序列化

然后進行加密

org.apache.shiro.mgt.AbstractRememberMeManager中的encrypt方法如下

protected byte[] encrypt(byte[] serialized) {
    byte[] value = serialized;
    CipherService cipherService = getCipherService();
    if (cipherService != null) {
        ByteSource byteSource = cipherService.encrypt(serialized, getEncryptionCipherKey());
        value = byteSource.getBytes();
    }
    return value;
}

ByteSource byteSource = cipherService.encrypt(serialized, getEncryptionCipherKey());調用的即為AES算法

可以看到使用CBC模式的AES加密算法,其中Padding規則是PKCS5。
具體實現在org/apache/shiro/crypto/JcaCipherService.java

private ByteSource encrypt(byte[] plaintext, byte[] key, byte[] iv, boolean prependIv) throws CryptoException {

    final int MODE = javax.crypto.Cipher.ENCRYPT_MODE;

    byte[] output;

    if (prependIv && iv != null && iv.length > 0) {

        byte[] encrypted = crypt(plaintext, key, iv, MODE);

        output = new byte[iv.length + encrypted.length];

        //now copy the iv bytes + encrypted bytes into one output array:

        // iv bytes:
        System.arraycopy(iv, 0, output, 0, iv.length);

        // + encrypted bytes:
        System.arraycopy(encrypted, 0, output, iv.length, encrypted.length);
    } else {
        output = crypt(plaintext, key, iv, MODE);
    }

    if (log.isTraceEnabled()) {
        log.trace("Incoming plaintext of size " + (plaintext != null ? plaintext.length : 0) + ".  Ciphertext " +
                "byte array is size " + (output != null ? output.length : 0));
    }

    return ByteSource.Util.bytes(output);
}

IV(初始化向量)是隨機生成的,將IV放在crtpt()加密的數據之前然后返回

加密結束后,在org/apache/shiro/web/mgt/CookieRememberMeManager.java的rememberSerializedIdentity方法中進行base64編碼,並通過response返回

這里的byte是前16位隨機的IV+AES密文,然后經過base64編碼

解析cookie

org/apache/shiro/web/mgt/CookieRememberMeManager.java中會將傳遞的base64字符串進行解碼后放到字節數組中,因為java的序列化字符串即為字節數組

byte[] decoded = Base64.decode(base64);

然后進入解密流程

先解密后進行反序列化

AES是對稱加密,加解密密鑰都是相同的,並且shiro都是將密鑰硬編碼

public void setCipherKey(byte[] cipherKey) {
    //Since this method should only be used in symmetric ciphers
    //(where the enc and dec keys are the same), set it on both:
    setEncryptionCipherKey(cipherKey);
    setDecryptionCipherKey(cipherKey);
}
public AbstractRememberMeManager() {
    this.serializer = new DefaultSerializer<PrincipalCollection>();
    this.cipherService = new AesCipherService();
    setCipherKey(DEFAULT_CIPHER_KEY_BYTES);
}

org/apache/shiro/crypto/JcaCipherService.java的decrypt()方法中進行解密從cookie中取出iv與加密的序列化數據

調用crypt方法利用密文,key,iv進行解密

解密完成后進入反序列化,看上面的public AbstractRememberMeManager()這里用的是默認反序列化類

public T deserialize(byte[] serialized) throws SerializationException {
    if (serialized == null) {
        String msg = "argument cannot be null.";
        throw new IllegalArgumentException(msg);
    }
    ByteArrayInputStream bais = new ByteArrayInputStream(serialized);
    BufferedInputStream bis = new BufferedInputStream(bais);
    try {
        ObjectInputStream ois = new ClassResolvingObjectInputStream(bis);
        @SuppressWarnings({"unchecked"})
        T deserialized = (T) ois.readObject();
        ois.close();
        return deserialized;
    } catch (Exception e) {
        String msg = "Unable to deserialze argument byte array.";
        throw new SerializationException(msg, e);
    }
}

readobject()觸發反序列化

至此,Shiro對Cookie的rememberMe的處理流程已整體調試分析結束。

漏洞修復

Apache Shiro 1.2.5版本的源碼,修復方法就是將使用默認Key加密改為生成隨機的Key加密:https://github.com/apache/shiro/commit/4d5bb000a7f3c02d8960b32e694a565c95976848

參考

https://ares-x.com/2020/04/20/IDEA遠程調試Docker中程序的方法/
https://paper.seebug.org/shiro-rememberme-1-2-4/
https://xz.aliyun.com/t/6493?accounttraceid=052d6170a05a4736a42c47de607a2766sdut#toc-6
https://www.mi1k7ea.com/2020/10/03/淺析Shiro-rememberMe反序列化漏洞(Shiro550)/


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM