1. 漏洞原理
Apache Shiro框架提供了记住密码的功能(RememberMe),用户登录成功后会生成经过加密并编码的cookie。在服务端对rememberMe的cookie值,先base64解码然后AES解密再反序列化,就导致了反序列化RCE漏洞。
那么,Payload产生的过程:
命令 -> 序列化 -> AES加密 -> base64编码 -> RememberMe Cookie值
在整个漏洞利用过程中,比较重要的是AES加密的密钥,如果没有修改默认的密钥那么就很容易就知道密钥了,Payload构造起来也是十分的简单。
2. 环境搭建
直接从github上clone代码到本地。
git clone https://github.com/apache/shiro.git
cd shiro
git checkout shiro-root-1.2.4
编辑shiro/samples/web目录下的pom.xml,加一个jstl的版本,否则默认版本解析jsp会报500错误。
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>jstl</artifactId>
<version>1.2</version>
<scope>provided</scope>
</dependency>
搭建tomcat。
下载shiro war包
https://github.com/jas502n/SHIRO-550
加载war包。
点击运行项目,打了一下,能打。
3. 漏洞流程分析
1、尝试登录shiro,把rememberMe勾上,会返回一大串Cookie,之后的请求都会带着这串Cookie。
这时候我们思考Cookie里面的内容是什么,一般都是用来存放一些认证信息,比如序列化好的一些认证数据等等。
2、在idea里面找一下数据是怎么处理的,两下shift,快速查找shiro包里面与cookie相关的类名。
3、CookieRememberMeManager类中和序列化相关的方法:rememberSerializedIdentity
CookieRememberMeManager类中和反序列化相关的方法:getRememberedSerializedIdentity()
getRememberedSerializedIdentity()的作用是获取序列化加密数据,像是只做了一层base64解密。
4、找哪里调用了getRememberedSerializedIdentity()。
AbstractRememberMeManager.getRememberedPrincipals()调用了getRememberedSerializedIdentity()
可以看到获取bytes以后调用了convertBytesToPrincipals()方法,该方法作用是把解密后的Cookie数据转换成认证信息。
跟一下convertBytesToPrincipals()方法,该方法作用是解密数据和反序列化数据。
跟decrypt()。
发现下列代码传入值分别是"加密的字段"和"加密key"。
cipherService.decrypt(encrypted, getDecryptionCipherKey());
找一下加密key是什么。
继续跟getDecryptionCipherKey(),发现返回decryptionCipherKey的值。
Find Usage decryptionCipherKey值,找哪里write了decryptionCipherKey,找到了setDecryptionCipherKey()方法。
继续找谁调用了setDecryptionCipherKey()方法,找到了setCipherKey()方法。
找谁调用了setCipherKey()方法,找到了AbstractRememberMeManager()方法。
发现常量DEFAULT_CIPHER_KEY_BYTES
发现是一个固定的key来做的aes加密。
跟deserialize()。
按住shift一直找,知道找寻到如下代码,可以看到调用了反序列化readObject(),这里如果有依赖的话就可以直接打CC了。
5、尝试找利用链,看起来java库里是带有有漏洞的CC的。
但是实际上用Maven Helper插件来看是没有真正运行到程序中的,都是存在于test包中。
程序实际运行只会把compile、runtime的包打进去,test打不进去。
真正能打的就这个CB。
6、这里就打URLDNS那个自带的链。
序列化数据 -> key固定aes加密 -> base64
生成URLDNS序列化数据,填写自定义dnslog地址。
使用脚本将序列化数据先aes加密,再base64。
生成加密的序列化数据后在发包利用的时候不能携带已登录的JSESSIONID,如果存在JSESSIONID就不会读取rememberMe了。直接发包,Cookie: rememberMe=[加密后序列化数据]
成功触发dnslog请求。
4. 参考
https://www.bilibili.com/video/BV1iF411b7bD/?spm_id_from=333.999.0.0&vd_source=04a73463f80a726a1d35327bd426fa6e
http://changxia3.com/2020/09/03/Shiro%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%E6%BC%8F%E6%B4%9E%E7%AC%94%E8%AE%B0%E4%B8%80%EF%BC%88%E5%8E%9F%E7%90%86%E7%AF%87%EF%BC%89/