下载地址:https://codeload.github.com/apache/shiro/zip/shiro-root-1.2.4java
环境:Tomcat 8.5.27 + idea 2020.2 + jdk 1.8 +maven 3.6git
下载以后以后直接打开,并open这个web文件夹便可,其余自行百度就行,其中还须要导入一些jstl的jar等等github
shiro默认使用了CookieRememberMeManager
,其处理cookie的流程是:web
获得rememberMe的cookie值 --> Base64解码 --> AES解密 --> 反序列化
然而AES的密钥是硬编码的,就致使了攻击者能够构造恶意数据形成反序列化的RCE漏洞。apache
payload 构造的顺序则就是相对的反着来:数组
恶意命令-->序列化-->AES加密-->base64编码-->发送cookie
在整个漏洞利用过程当中,比较重要的是AES加密的密钥,该秘钥默认是默认硬编码的,因此若是没有修改默认的密钥,就本身能够生成恶意构造的cookie了。cookie
shiro特征:session
复现文章https://blog.csdn.net/weixin_43571641/article/details/108182722框架
简单介绍利用:maven
那既然咱们要分析,那入口点在哪呢?Shiro≤1.2.4版本默认使用CookieRememberMeManager
而咱们看看这边CookieRememberMeManager
类继承了AbstractRememberMeManager
,咱们进去看看是什么梗
咱们能够看到这边这个类里面有硬编码。而后它又继承了RememberMeManager
接口;咱们继续进去看看是怎么回事
看名字的话能够知道这些是登录成功,登录失败,退出的一些service;既然如此,确定会调用这个登录成功的接口,而后再去实现这个接口。因此咱们直接在这个接口下个断点,看看是怎么个流程;
这里看到调用了isRememberMe()
能够发现这个就是一个判断用户是否选择了RememberMe
选项。而咱们是勾选了的
因此咱们咱们条件知足,这边判断返回True,咱们则进入this.rememberIdentity(subject, token, info);
subject
存储的一些登录信息如session等等,而authcInfo
存储的则是用户名;
而PrincipalCollection是一个身份集合,由于咱们能够在Shiro中同时配置多个Realm,因此呢身份信息可能就有多个;所以其提供了PrincipalCollection用于聚合这些身份信息,具体咱们不细讲,不深刻去懂原理。
而后咱们再F7继续跟进this.rememberIdentity(subject, principals);
这咱们有点懵,将身份信息干吗?咱们进入该convertPrincipalsToBytes()
方法查看;
看到了serialize()
方法,难道这边开始是进行序列化了仍是啥?
经过此处咱们能够知道是跳了两层,到DefaultSerializer
类的serialize
方法;看到这里就懂了,这里先转为byte,写入缓冲区;而后进行了一个序列化,最后经过toByteArray()
方法返回序列化后的Byte数组。
而后返回到原来的地方convertPrincipalsToBytes()
内,接下来if判断getCipherService()
方法不为空,则进入条件里面里面。咱们f7进去内部看看;
发现又是一个cipherService
,这是什么;咱们翻译一下,由于大部分开发都会用简称;
也就是获取密码服务?? 什么密码服务?咱们再继续F7跟进发现直接推出了。那咱们就 Ctrl+左键
继续进去看。能够,发现是new了一个aes加密服务。
那咱们点击debugger处,回到刚刚那个地方;咱们就不用继续进入了,咱们就思考一下,这边是要获取到加密服务,若是没获取到,则不进入。获取到的话,则进入该条件;
直接F8下来,进入,而后咱们再手动添加变量监视。能够发现正如咱们所想的,获取aes加密服务;
而后调用encrypt()
方法,而懂点英文的,都知道这个单词是加密的意思。那咱们初步判断这是个加密方法。咱们f7跟进去看看什么状况。
咱们能够知道这个参数是byte[] serialized
,也就是说,此处加密咱们刚刚的序列化流的数据。
而后这边this.getCipherService()
咱们刚刚手动添加变量查看了,这边是获取到了aes加密服务;而后判断不问空,那确定不为空啊,刚刚上面分析过了。而后咱们进入条件判断股内部。
ByteSource byteSource = cipherService.encrypt(serialized, this.getEncryptionCipherKey());
这里调用cipherService.encrypt()
方法而且传入序列化数据,和getEncryptionCipherKey
方法。加密过程,咱们就应该不怎么感兴趣了;有兴趣的能够本身研究
咱们经过getEncryptionCipherKey()
名字能够知道是获取key的一个方法。那咱们f7进入看看
哦豁,那咱们再进一层看一下;发现直接就返回了,emmmmm....怎么跟别人不同。那咱们就不追了
第一步有说到,硬编码存储在这个地方,而构造方法就在这下面,能够看到这边设置了key。
咱们继续回到原来的地方,知道这边是获取加密的key就ok了。而后这边使用平台的默认字符集将字符串编码为 byte 序列,并将结果存储到一个新的 byte 数组中。那咱们加密部分就结束了
因为此处,我找不到,回溯不到,那咋办,烦恼;最后想到了咱们加密的入口~~
既然自动跳到了这里,那么咱们就直接在此处下个断点,从新开始
随后咱们进入这个getRememberedSerializedIdentity()
方法,看看是什么东西。此处咱们依然还很懵,没事;
一直f8,期间却是没有什么有意思或者重点的地方;
直到咱们走到这里,这个有一个this.getCookie().readValue(request, response)
,这是要读取cookice中的数据了,这必须跟入了;
这里给进到了这个readvalue()
方法中了,咱们先看看什么状况。根据名字能够知道是读取值的一个方法。读取什么值?请求包的值。
经过getName()
方法获得了key为remeberMe。而后把value置空,再经过getCookie
获取到cookie。最后判断cookie不为空,则进入内部;随后获取到cookie的值;值则为序列化内容。而后再 return回序列化内容;
随后返回到上一处地方如今remeberMe
的值不是delete
;而是序列化内容,因此进入到第二个条件分支。
一直到这一步,进行base64解码,成为二进制数据,给了decoded的byte数组;
获得rememberMe的cookie值 --> Base64解码 --> AES解密 --> 反序列化
目前只进行了Base64解码,那还须要aes解码。咱们继续跟进
返回到了上层,此处咱们知道bytes是二进制数据,咱们看看条件判断。当bytes数组不为空且长度大于0时,进入里面。那咱们确定知足,因此咱们两步f8加一步F7进入到 convertBytesToPrincipals
看看是什么
能够看出咱们接下来的步骤要依依实现了。判断key不为空,而后进入内部
而从这里开始,就是进行aes解密的步骤了,咱们F7跟进方法查看
这里从新把恶意的bytes数组从新赋值给serialized
,而后再获取加密服务:AES/CBC/PKCS5Padding
同时到达了下一步;真真正正的开始解密了,其中两个参数,第一个是加密的bytes数组,第二个是获取到key,也就是硬编码;咱们 就直接进入decrypt()
方法中
解密过程的话,我不擅长密码学,这种看着我头晕,涉及到aes啥的加密解密我就会跳过。因此依旧同样,跳!!!
此处继续返回到了上一层,咱们能够看出这个byteSource是aes解密出来的序列化流,而后再默认字符集将字符串编码为 byte 序列,并将结果存储到一个新的 byte 数组serialized
中,那接下来咱们就差反序列化了
获得rememberMe的cookie值 --> Base64解码 --> AES解密 --> 反序列化
咱们继续return,返回到上一层
顾名思义,一看名字就知道是反序列化的方法,咱们跟进deserialize()
方法查看
看到还有一层,咱们继续F7跟进
造成反序列化漏洞的话,没有readObject()
怎么可能呢?因此咱们看到了最后一道光,就这么愉快的结束了。
其实这个仍是得学习学习加密解密的方法,才能进行编写poc,可是此处只是了解个思路。具体可参考其余文章;我空余时间的话,会继续补全该篇文章,先去搞其余的玩意儿
[P牛博客](https://zeo.cool/2020/09/03/Shiro 550 反序列化漏洞 详细分析+poc编写/#解密过程:)