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