0x00、前言
原理篇到Shiro部分,继续学习分析
0x01、Shiro描述
引用官方的描述:
Apache Shiro是一个强大的和易于使用的Java安全框架,执行认证、授权、加密和会议管理和可用于安全的任何应用程序从命令行应用程序、移动应用程序的最大网络和企业应用程序。
Shiro提供程序安全API执行以下方面:
- 认证-证明用户身份、通常称为用户”登录”
- 授权-访问控制
- 密码保护-从监视中保护或隐藏的数据
- 会议管理-每个用户时间敏感的状态
Shiro官网:https://shiro.apache.org/
Shiro有三个核心组件:Subject, SecurityManager 和 Realms.

Subject:即“当前操作用户”。但是,在Shiro中,Subject这一概念并不仅仅指人,也可以是第三方进程、后台帐户(Daemon Account)或其他类似事物。它仅仅意味着“当前跟软件交互的东西”。
Subject代表了当前用户的安全操作,SecurityManager则管理所有用户的安全操作。
SecurityManager:它是Shiro框架的核心,典型的Facade模式,Shiro通过SecurityManager来管理内部组件实例,并通过它来提供安全管理的各种服务。
Realm: Realm充当了Shiro与应用安全数据间的“桥梁”或者“连接器”。也就是说,当对用户执行认证(登录)和授权(访问控制)验证时,Shiro会从应用配置的Realm中查找用户及其权限信息。
更多描述可以查看这篇文章
0x02、环境搭建
可以直接从Github下载shiro漏洞环境或者从vulhub下载,https://github.com/apache/shiro
项目丢进idea后,添加运行配置添加tomcat服务器,添加war包


再更改pom.xml文件,讲jstl改为1.2版本,再添加利用链的包


<dependencies> |
同步更新完mvn后,开启服务即可

0x02、Shiro550漏洞分析
漏洞范围
- Shiro <= 1.2.4
漏洞原理
Shiro采用AES对称加密,将默认密钥key硬编码在代码中,用户使用rememberMe功能提交数据时,服务器会将rememberMe值使用默认密钥key进行AES解密,将解密后的字符串进行反序列化读取,用户使用硬编码的key进行AES加密后发送给服务器进行AES解密,服务器反序列化读取时触发漏洞。
原理很好理解,核心问题就是硬编码Key,用户知道key并且服务器存在利用链就能执行恶意代码
漏洞分析
这里可以直接模拟工具发送payload进行断点分析,如果选择暴破的话,第一个包为rememberMe=yes,来判断是否使用shiro组件,然后再是使用key进行加密发送,这里直接分析key加密的数据包

在rememberMe核心处理方法(AbstractRememberMeManager#getRememberedPrincipals)进行断点分析

该方法首先会调用getRememberedSerializedIdentity方法处理数据内容,跟进
该方法获取数据内容的请求和响应内容然后读取cookie

跟进readValue方法,获取请求头中的cookie,然后再获取cookie值,最后返回值

接着base64解码rememberMe值

这里就是base64解码的流程,先转化成字节,然后进行解码

最后返回字节码数组

然后调用convertBytesToPrincipals方法,跟进,调用decrypt解码字节数据

该方法中会调用AES解码,AES解码的Key通过getDecryptionCipherKey()方法获取

getDecryptionCipherKey()方法直接返回了key值,也就是说Key值在调用getDecryptionCipherKey方法前就已经复制给了变量

这里正向解析清楚一些,在调用AbstractRememberMeManager#getDecryptionCipherKey方法时,AbstractRememberMeManager实例化会调用构造方法

其中会调用setCipherKey方法讲默认值进行传递,默认值就是默认的Key值进行base64转换从字节数组

setCipherKey方法会将key传递给setEncryptionCipherKey和setDecryptionCipherKey方法设置为加解密的值

然后setDecryptionCipherKey方法将key复制给decryptionCipherKey,最后getDecryptionCipherKey方法获取到解密key值

回到decrypt方法,获取到解密key后,调用AES的decrypt方法进行解码
计算出iv值(初始化向量),再调用带iv参数的decrypt方法

跟进后,调用crypt方法解析内容

跟进crypt方法,调用initNewCipher方法获取cipher(获取加密方式的对象)


调用doFinal方法解码数据,返回解析内容

上一层获取后,经过转换为字节码,然后返回序列化数据


返回给上一层,拿到序列化数据后,进行反序列化

跟进反序列化方法,获取默认反序列化器然后调用反序列化器的deserialize反序列化方法

进行反序列化,也就是漏洞触发点

最后返回序列化对象,如果反序列化过程抛出异常,则会最后会调用removeFrom方法将rememberMe=deleteMe添加到响应头当中,导致key检测失败

避免这个问题,需要将设定不会报错的序列化类(属于PrincipalCollection类),可以将恶意类生成的对象属于PrincipalCollection类,返回序列化对象即可

工具暴破key使用SimplePrincipalCollection类来规避这个问题,因此反序列化过程不会报错

直到执行结束,响应头中没有rememberMe=deleteMe字段,证明Key正确,显示出暴破出的key

如果key错误,在解码过程中会抛出异常

抛出异常的过程中最后会调用removeFrom方法将rememberMe=deleteMe添加到响应头当中

利用链流程一样,在readObject时触发漏洞,执行完成,链存在,然后再抛出异常反序列化数据不可用,但反序列化已经执行,报错问题不重要

不用工具的利用方式,将利用链进行bytes编码,然后和key值经过base64解码成字节数组,使用AES加密即可得到rememberMe字段值
流程总结
shiro处理过程:
获取请求内容,读取cookie值 |
检测key过程:
使用SimplePrincipalCollection类序列化数据,使用Key进行AES加密后发送,如果响应头不存在rememberMe=deleteMe字段,则表明key正确,反之key错误。
0x03、总结
Shiro550原理理解起来比较简单,调用过程也没有那么复杂,核心问题还是硬编码key。
0x04、参考链接
https://blog.csdn.net/MinggeQingchun/article/details/126414384
https://www.yuque.com/tianxiadamutou/zcfd4v/op3c7v