什么是shiro
Apache Shiro 是一个开源的 Java 安全框架,用于实现身份验证、授权、加密和会话管理等功能。它设计简单、易用,广泛应用于企业级应用的权限管理和安全控制。Shiro 的核心特点包括:
- 身份验证(Authentication):验证用户身份(如用户名/密码登录)。
- 授权(Authorization):控制用户访问权限。
- 会话管理(Session Management):管理用户会话,支持 Web 和非 Web 环境。
- 加密(Cryptography):提供数据加密和解密功能。
- 灵活性:支持与各种技术栈(如 Spring、Java EE)集成。
Shiro 常用于 Web 应用、微服务或企业系统的安全管理,简化了权限控制的开发。
shiro反序列化分为两种,shiro-550和shiro-721
环境搭建
首先需要下载tomcat,具体下载参考网上文章,由于用的jdk是1.8版本,所以tomcat版本不能太高,这里下载的是tomcat 8.5.6
输入startup启动,启动不成功可能是端口被占用,将/conf/server.xml中的Conection port更改后重启即可
由于我用的idea是社区版,所以需要下载一个tomcat插件
后续搭建环境发现大家用的都是专业版,社区版复现很麻烦,于是只能搞一个专业版了,这是环境搭建教程
直接下载现成的war包
https://github.com/jas502n/SHIRO-550
shiro-550
漏洞原理是cookie中存有rememberMe字段,服务器会对这个字段进行base64解密后再进行AES解密,最后进行反序列化,问题出在AES上,密钥是固定的,这意味着我们可以构造任意序列化的数据,造成任意执行
基础环境
编辑器:IDEA 2024
java版本:jdk1.8.0_65
Server版本 : Tomcat 8.5.56
shiro版本:shiro-root-1.2.4
漏洞复现
登录页面抓个包,发现cookie中加入了rememberMe字段
进shiro源码全局搜cookie,在CookieRememberMeManager类中发现shiro对cookie的处理是先进行序列化,AES加密,再进行base64编码处理,所以我们可以利用这个点来反过来打一个反序列化。
查找rememberSerializedIdentity用法,来到AbstractRememberMeManager类,发现一个硬编码的密钥
发现这个密钥作为AES的默认密钥
问题点就出在这里,硬编码的密钥使我们可以任意加解密任意代码,从而造成RCE
接下来我们下个断点,跟进看一下登录的过程整个对cookie的处理过程
在org.apache.shiro.mgt.AbstractRememberMeManager的onSuccessfulLogin函数下个断点,然后debug开启tomcat服务
去登录框输入root/secret,程序停止在onSuccessfulLogin函数
跟进forgetIdentity函数,发现是对一些http的request和response相关请求
接着跟进,进到rememberIdentity函数
rememberIdentity函数首先调用getIdentityToRemember函数来获取用户身份,这里也就是"root",接着我们跟进rememberIdentity构造方法:
进入convertPrincipalsToBytes函数,发现核心处理逻辑,先序列化后AES加密,跟进继续看一下,可以知道使用的是AES-CBC模式加密
接着跟进,进到CookieRememberMeManager类中,将加密后数据进行base64加密后放到cookie里
解密cookie的逻辑其实就是把这个顺序倒一下,就没必要再跟了。
测试了一下用下面的工具也能打通
shiro721
从本质上来说,跟shiro550的攻击流程是一样的,区别在于550可以直接获取到密钥,而721则需要通过padding Oracle攻击破解AES算法这部分
padding oracle attack
我们知道AES的每一块都是十六字节,在加密时如果长度不够会进行填充,解密时将填充内容去掉,去除填充可能成功也可能失败,如果服务器将无论成功还是失败的信息都返回,就达成了攻击的利用条件。
攻击需要满足的条件
- 填充采用pkcs5填充标准,简单说就是如果差五个字节,我就将最后五个字节都填充为05,如果差3个字节,我就将最后三个字节都填充为03
- 采用CBC模式分组
攻击者能得到的信息
- 攻击者能够获得密文,以及附带在密文前面的初始向量IV
- 攻击者可以和padding oracle进行交互,通过发送密文获知padding是否正确
攻击达到的效果
- 在不知道key的前提下解密任意给定的密文
$c_i$在经过解密之后形成中间值,接下来要与$c_{i-1}$进行异或,我们姑且将它称作当前密文和前一段密文好了,前一段密文和中间值进行异或,我们通过不断调整前一段密文的最后一个字节(可以穷举搜索),使异或后的明文最后一个字节为01,进入到填充逻辑,服务器就会返回填充正确。有这个填充正确,我们就可以通过01和前一段密文构造字节的异或,得到中间值的最后一个字节,将中间值的最后一个字节和未调整时的前一段密文的最后一字节异或,就能得到明文的最后一个字节。
我们用同样的思路,爆破前一段密文的倒数第二个字节,使填充逻辑到末尾两个字节都是02,服务器返回填充正确,接着将02和前一段密文异或拿到中间值的倒数第二个字节,再通过中间值的倒数第二个字节与原始前一段密文异或拿到明文的倒数第二个字节
以此类推,拿到明文的所有字节。
真绕啊,想不清楚的点让ai给你通俗解释一下里面的逻辑。
回到shiro,那么这种攻击在shiro反序列化里面怎么用呢,在shiro721里,如果填充错误,服务器返回Set-Cookie: rememberMe=deleteMe
,反之,则服务器正常相应。
shiro工具利用
半路上遇到一个GitHub的shiro工具,顺路记载一下
下载地址
下载后运行java -jar ShiroExploit.jar即可运行,当然也可以写一个bat脚本,无黑框版
@echo off
start javaw -jar ShiroExploit.jar
exit
Shiro550
无需提供rememberMe Cookie,Shiro721
需要提供一个有效的rememberMe Cookie- 可以手工指定特定的 Key/Gadget/EchoType(支持多选),如果不指定会遍历所有的 Key/Gadget/EchoType
- 复杂Http请求支持直接粘贴数据包