shiro反序列化
前言
Apache Shiro 是一个强大且易于使用的 Java 安全框架,提供了身份验证、授权、会话管理和加密等安全功能。
Shiro框架在1.2.5之前产生的反序列化漏洞被称为:shiro-550
在1.2.5之后(1.2.5, 1.2.6, 1.3.0, 1.3.1, 1.3.2, 1.4.0-RC2, 1.4.0, 1.4.1)的反序列化漏洞被称为:shiro-721
环境配置
本人近几天一直头疼于怎么配置这个shiro550的源码环境,这里我给出详细的步骤,以免后人再踩进我踩过的大坑…
首先,下载p神的shiro源码:
https://github.com/phith0n/JavaThings/
下载好之后用IDEA打开这个shirodemo。
我的JDK版本:8u66,正常来说pom.xml里面是不会爆红的。
接着去配置Tomcat,这个步骤比较简单,网上教程也很多,这里不多赘述,推荐:
IntelliJ IDEA中配置Tomcat(超详细)_intellij idea配置tomcat-CSDN博客
我下载的是9.0.100版本。
在项目中配置
接着来到项目结构的工件这里:
可以看到已经有两个部署好的工件,选择下面这个,点应用。
编辑配置里头新建本地Tomcat:
点击应用后直接运行login.jsp,记得修改下它的端口号。
然后我们点击这里:
即可启动。
漏洞分析
数据包特点
在登录框中我们先简单地抓个包(点击了Remember me):
没点击的包:
相比之下得出结论:
勾选了remember me之后,在返回的数据包上多了Set-Cookie字段,第一个值为deleteMe
,第二个像是一串Base64。
1 | 6nTT7Ep5Grk1dN3I5KgZ1iTjjdZnS2+zSoEcYP69yZ7n+r2mYNzMXMXY1WvN99zuOQlrEsv1uHH9UOuTO0fyQjg0v0rh4RVzw4V4SANKcQSkSsFX7V8CSelOgTmiBX+3goGAsbhFGonZIE0nSlqprPgLsybCTW5hdJf/FbE6hC2g1xnoNHbuJ6Lj+BBgRY4IYmSm7kz2AhvtioQi6k6AsDAayvnZKwQlSIzH8Y4s2lEt6vxndTjQcHIngWQTxGZxjmXnQuOrsu6IeLtlRLpNzYSRw0RtXe3T5SifdcBXs5JC15ljEoRJqkO8GBT/8u1RfUNAXW9+stYrDbxBoQRO5bOZWZq/AHUXkACTiGJsiL17WjGYkT/Ec022fmdiZPGnvrLWeaOQeDXCFfg4IqSCH4tnHzt1kpwncCdSwpEbkbWb9/5/v1ZM3Ejz1j/KQI4Winhwytrz2cZq83CcITV1D1YeUGW4c0kVLPFu8NWVwM8hvxtmIphLYQgKU3xbe8tK |
试图解码一下发现并不是base64,下面我们来看看Shiro框架(550版本)的加解密过程。
过程分析
我们先定位到CookieRememberMeManager类:
CookieRememberMeManager类
构造方法:
1 | /** |
getRememberedSerializedIdentity()
:
长话短说:
- 先判断是否为 HTTP 请求
- 如果是的话,获取 cookie 中 rememberMe 的值
- 然后判断是否是 deleteMe,如果存在,则返回null。
- 如果不是,则判断是否是符合 base64 的编码长度,然后再对其进行 base64 解码,将解码结果返回。
AbstractRememberMeManager类
现在我们去寻找一下谁会调用CookieRememberMeManager#getRememberedSerializedIdentity()
getRememberedPrincipals()
- 首先是将 HTTP Requests 里面的 Cookie 拿出来,赋值给 bytes 数组;
- 随后将 bytes 数组的东西进行
convertBytesToPrincipals()
转换成principals(这是什么?)
我们跟进到神秘的convertBytesToPrincipals():
convertBytesToPrincipals()
正常情况下这里调用了decrypt()
与deserialize()
看看decrypt()
decrypt()
1 | /** |
既然有decrypt函数,肯定也有encrypt函数。我们把断点下在encrypt函数:
在单步跟的过程中可以知道encrypt采用的是AES加密:
而且AES的key是一个固定值,写在了本类的源码中:
deserialize()
1 | /** |
经过断点调试,deserialize()
会调用readObject()
。
攻击实现
攻击原理
为了让浏览器或服务器重启后用户不丢失登录状态,Shiro支持将持久化信息序列化并加密后保存在Cookie的rememberMe字 段中,下次读取时进行解密再反序列化。但是在Shiro 1.2.4版本之前内置了一个默认且固定的加密 Key,又因为加密算法是AES(对称加密),key既能加密也能解密,导致攻击者可以伪造任意的rememberMe Cookie,进而触发反序列化漏洞。
AES加密脚本
根据shiro550的加密流程,编写以下EXP:
1 |
|
攻击演示
URLDNS
URLDNS无需任何依赖,可以直接测试是否触发反序列化漏洞。
我们现在Burpsuite中开启Collaborator模块进行检测,把url丢进下面的URL类构造方法中(具体请见URLDNS篇)
URLDNS链子:
1 | import java.io.*; |
将生成的DNS.bin放入AES脚本的目录下,执行:
这一大坨存起来。我们进入shiro登录框,先勾选remember me,在登录root/secret,登录成功后,数据包返回了rememberme的字段给我们,这之后访问shiro框架都会带着这个cookie字段,此时可以抓包修改cookie为我们的恶意密文,在服务端进行解密后,又进行了反序列化,导致链子被触发:
shiro-721漏洞
漏洞原理
在shiro1.2.5及以后的版本中,shiro采用的加密方式是AES-CBC,key值是系统随机生成的,但是可以通过Padding Oracle Attack攻击可以实现破解AES-CBC加密过程进而实现rememberMe的内容伪造。
shiro-721对cookie中rememberMe字段的解析过程:
这种AES会受到Padding Oracle Attack(填充提示攻击),能够利用有效的RememberMe Cookie作为Padding Oracle Attack的前缀,然后精心构造RememberMe Cookie值可以实现反序列化漏洞攻击。实质是跟Shiro-550一样的。