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)
白日梦组长