Java学习之Shiro550
环境
- jdk8u65
- Tomcat8
- shiro 1.2.4
漏洞影响版本:Shiro <= 1.2.4
环境具体的配置参考:Java反序列化Shiro篇01-Shiro550流程分析 | Drunkbaby’s Blog (drun1baby.top)
shiro-550分析
哎,终于到了shiro,虽然很少遇到这个漏洞但是这个还是挺重要的
抓个登录的包,勾选 RememberMe 字段
可以看到这个cookie明显是通过加密的,那么就去分析一下这个cookie的生成过程,idae里面按两下shift可以输入cookie然后会把和cookie这个名字有关的类列出来,最终我们找到了CookieRememberMeManager类中的 getRememberedSerializedIdentity()方法
这个方法先判断是否为http请求,如果是则会获取remember me的cookie值,然后判断是否是deleteMe,不是则判断是否是符合 base64 编码,然后再对其进行 base64 解码,将解码结果返回,那么往上找谁调用了这个方法
找到了AbstractRememberMeManager 这个类的 getRememberedPrincipals() 方法,这里要把对应的源码下载find usages才能搜到,这个方法的作用域为 PrincipalCollection,一般就是用于聚合多个 Realm 配置的集合。393行将 HTTP Requests 里面的 Cookie 拿出来,赋值给 bytes 数组;396 行将 bytes 数组的东西进行 convertBytesToPrincipals() 方法的调用,并将值赋给 principals 变量,convertBytesToPrincipals方法将之前的 bytes 数组转换成了认证信息,并且对其进行解密和反序列化
decrypt()解密
我们跟进一下decrypt()方法
487 行这里获取了密钥服务,再往后 489 行的 decrypt() 跟进去,发现它是一个接口。我们可以看一下它的参数名第一个要加密的数组,第二个是一个 key,说明这是一个对称加密
回到之前 decrypt() 方法的第 489 行,两个传参,第一个是加密字段,第二个是 key,跟进传入的 getDecryptionCipherKey()最终发现它是一个返回的常量decryptionCipherKey
然后find usages寻找哪里调用了它,在同类下的setDecryptionCipherKey()里找到了它的调用
然后再找一下setDecryptionCipherKey()的调用,在同类下的setCipherKey()方法中调用了
然后继续看哪里调用了setCipherKey()
到这里跟进去发现DEFAULT_CIPHER_KEY_BYTES是个固定的值
也就是说在Shiro1.2.4 及之前的版本中对remember me加密的key是一个固定的key,其实加密算法就是AES加密
deserialize反序列化
来到一开始的convertBytesToPrincipals()方法找到deserialize()方法点进来到下面这里
这里再点进去其实是一个接口,然后继续查看哪里调用了
在shiro包的deserialize方法中调用了readObject()方法后面就会进到HashMap这就是一个很好的入口,到这里解密的过程就结束了,接下来去跟一下加密的过程
加密
加密通过调试来分析,断点下在如下图onSuccessfulLogin()位置
这里怎么进到这个断点呢?就是我们在一开始登录的页面开启代理
当断点下好之后点击sign in即可到断点位置,当然也可用burp发包开始调试,在如下位置对是否点击了Remember Me进行判断,如下点击了会将rememberMe赋值为true
然后到这个rememberIdentity方法,这里就是用于用户名赋值保存,可以看到这里一系列调用完之后得到了用户名
然后进入到rememberIdentity方法继续看
这里的convertPrincipalsToBytes和之前解密的convertBytesToPrincipals()非常相似不过解密变为了加密,反序列化变为了序列化
然后进入到序列化的这里(到上面这一步之后一直F7就行)可以看到和反序列化过程一样
然后回到 encrypt 加密这里,进入getEncryptionCipherKey()方法
一直跟下去之后发现最后调用的key就是我们之前找到的那个常量
cookie通过AES加密之后再base64得到,到这里差不多分析结束
Shiro-550 漏洞利用
这里用了一个插件名为Maven Helper分析pom.xml
其实这里有很多包在test下,看组长的视频说test是打不到的,但是不知道为什么我的插件没有显示很奇怪
URLDNS链
这里我们的攻击思路就是直接替换加密后的cookie,那么就需要一个加密的脚本,这里直接用Drunkbaby师傅的脚本
1 | # -*-* coding:utf-8 |
然后因为URLSDNS这条链不依赖于CC包,所以用来验证漏洞,那么先将payload通过AES加密然后base64,exp如下是之前跟URLDNS这条链子写的
1 | import java.lang.reflect.Field; |
然后反序列化那里先注释掉,直接序列化得到ser.bin,然后把这个序列化的数据放到加密脚本里面跑得到加密cookie
最后把这段cookie替换掉原来的cookie,主要这里的JSESSIONID这段数据一定要删不然不会去读rememberMe
发包之后收到DNS请求
接下来就是换不同的链尝试攻击
通过 CC11 链攻击
还是用之前的CC11链子攻击,先序列化ser.bin然后加密发包就行
通过 CB1 链攻击
这里需要注意的是CB版本问题,一开始用的exp是之前跟的CB1这条链的时候写的,但是那个CB版本是1.9.2,yso 中的链子也是,而shiro自带为 1.8.3,所以需要把版本更换一下不然服务端就好报如下错误
1 | org.apache.commons.beanutils.BeanComparator; local class incompatible: stream classdesc serialVersionUID = -2044202215314119608, local class serialVersionUID = -3490850999041592962 |
这个serialVersionUID就是Java的一个机制,在Java在反序列化的时候提供了一个机制,序列化时会根据固定算法计算出一个当前类的 serialVersionUID 值,写入数据流中;反序列化时,如果发现对方的环境中这个类计算出的 serialVersionUID 不同,则反序列化就会异常退出
AES密钥判断
这里还有一个问题参考Drunkbaby师傅的描述就是shiro 在 1.4.2 版本之前, AES 的模式为 CBC, IV 是随机生成的,并且 IV 并没有真正使用起来,所以整个 AES 加解密过程的 key 就很重要了,正是因为 AES 使用 Key 泄漏导致反序列化的 cookie 可控,从而引发反序列化漏洞。在 1.4.2 版本后,shiro 已经更换加密模式 AES-CBC 为 AES-GCM,脚本编写时就需要考虑加密模式变化的情况。记得之前用过的飞鸿大佬的一款shiro反序列化利用工具就做了key的爆破,下面是大佬 Veraxy 的脚本
1 | import base64 |
参考:
Java反序列化Shiro篇01-Shiro550流程分析 | Drunkbaby’s Blog (drun1baby.top)
白日梦组长





