java安全-Shiro550漏洞原理学习与分析
2023-02-20 16:41:00

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>
<dependency>
<!-- 添加可以利用的jar包-->
<groupId>org.apache.commons</groupId>
<artifactId>commons-collections4</artifactId>
<version>4.0</version>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>jstl</artifactId>
<!-- 这里需要将jstl设置为1.2 -->
<version>1.2</version>
<scope>runtime</scope>
</dependency>
</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传递给setEncryptionCipherKeysetDecryptionCipherKey方法设置为加解密的值

然后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值
->通过Base64解码将值转换从字节数组
->调用AES解码,用默认key对字节数组进行解码(key如果错误,抛出异常,向cookie头中添加rememberMe=deleteMe)
->计算初始化向量iv,然后带入AES-CBC模式进行解码返回原始序列化数据
->使用默认反序列化器对序列化数据进行反序列化读取
->调用readObject反序列化数据触发漏洞

检测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