Shiro 550/CVE-2016-4437
环境
1 | commons-beautils:1.8.3 |
现成配置的话可以参考这篇文章:https://blog.csdn.net/qq_47886905/article/details/123479769
漏洞描述
为了让浏览器或服务器重启后⽤户不丢失登录状态,Shiro⽀持将持久化信息序列化并加密后保存在Cookie的rememberMe字段 中,下次读取时进⾏解密再反序列化。但是在Shiro 1.2.4版本之前内置了⼀个默认且固定的加密Key,导致攻击者可以伪造任意 的rememberMe Cookie,进⽽触发反序列化漏洞。
简单介绍利用:
通过在cookie的rememberMe字段中插入恶意payload,
触发shiro框架的rememberMe的反序列化功能,导致任意代码执行。
shiro 1.2.24中,提供了硬编码的AES密钥:kPH+bIxk5D2deZiIxcaaaA==
由于开发人员未修改AES密钥而直接使用Shiro框架,导致了该问题
利用条件
Shiro < 1.2.4
相关代码分析
我们从CookieRememberMeManager这个类开始进行分析
定义了一个默认cookie名称的字段以及一个Cookie类的对象cookie
继续看看它的构造方法:
发现这里实例化了一个SimpleCookie类,并将rememberMe作为参数传入,跟进
这个类主要是定义了一些与cookie相关的基础属性
这个构造方法并没有什么特别的地方,总的来说CookieRememberMeManager的构造方法就是得到一个cookie对象,并赋予其基础属性值。
我们可以继续去看另一个类:AbstractRememberMeManager
这是一个抽象类,实现了RememberMeManager接口,在这个类中,我们还可以发现默认的密钥
有点好奇,解码一下是这样的
我们继续关注一下这个类重写的RememberMeManager接口的onSuccessfulLogin方法,根据这个名字猜测应该是在登录成功后所执行的方法
首先验证了token的正确性,如果验证成功,那么就调用rememberIdentity方法,跟进
继续调用它的重载方法
定义了byte类型数组,并调用了convertPrincipalsToBytes方法,跟进
调用了一个serialize方法,跟进
又有一个getSerializer()方法,看看
返回一个Serializer对象,那么serialize的方法相当于就是调用了Serialize类的serialize方法,跟进
该接口定义了序列化与反序列化两种方法,继续看看这个接口的默认实现类DefaultSerializer,重写了serialize与deserialize两个方法,内部详细代码就是普通的序列化与反序列化过程,没有什么特别的
重新回到convertPrincipalsToBytes方法,当getCipherService()不为null时,进一步调用encrypt方法(稍微跟进一下getCipherService()方法,可以发现它默认并不为null)
这里的cipherService默认为AesCipherService对象,但是AesCipherService类并没有encrypt方法,进过几层继承关系,最后会调用到JcaCipherService的encrypt方法,好像是个AES加密,我也看不懂,就不分析了。
回到rememberIdentity方法,调用了rememberSerializedIdentity方法
这个抽象方法的实现在CookieRememberMeManager类中
就是进行一个base64编码,然后存储到cookie中
浅浅总结
我们发现cookie的解密是于反序列化相关的,那么我们就可以传一个恶意的反序列化代码,当shiro解析时,便会触发该链,造成命令执行。
而这一过程是如何实现的呢?
我们可以定位到CookieRememberMeManager类的getRememberedSerializedIdentity⽅法
对cookie进行一个base64解密,然后就应该进行AES解密了,向上寻找调用该方法的类,找到AbstractRememberMeManager类的getRememberedPrincipals方法
在调用getRememberedSerializedIdentity方法也就是进行base64解码后,继续调用convertBytesToPrincipals方法
进行AES解密后,便调用了反序列化操作,这里也就是漏洞点,而RememberMeManager接口定义了该方法,也就是说,每传入一次cookie,便会调用getRememberedPrincipals方法
代码调试验证
加密
首先在RememberMeManager接口的onSuccessfulLogin处下个断点
开始调试,登录,勾选rememberme
可以看到已经接收到了数据,继续步入,进入CookieRememberMeManager的forgetIdentity方法,处理request和response请求
继续进入forgetIdentity重载方法
调用getCookie的removeFrom方法,跟进removeFrom方法
对返回包的一些操作,其中就有我们熟悉的,deleteMe字段和rememberMe字段,也就是我们指纹识别最简单的两种方法的原理
回到onSuccessfulLogin方法
先检验token,随后步入 rememberIdentity 方法,看看做了什么
在rememberIdentity方法中,authcInfo的值就是我们输入root用户名,继续跟进rememberIdentity函数
进入rememberIdentity方法后发现,一个函数就是转化为bytes,跟进convertPrincipalsToBytes
这里便是刚刚说的序列化以及AES加密过程,就不详细分析了
回溯,进入rememberSerializedIdentity方法,也就是进行base64编码后保存到cookie
到这差不多就是完整的加密过程
解密
现在继续研究解密过程:
- 首先确定切入点
- 我选择从获取到客户端数据开始分析 ,那就是 org.apache.shiro.mgt.AbstractRememberMeManager 类的 getRememberedPrincipals 方法下断点
- 随后在页面随便刷新一下,就可以触发这个方法
直接跟进 getRememberedSerializedIdentity(subjectContext) 方法,看看从数据中,都获取了什么
继续调用getRememberedSerializedIdentity方法,应该是获取cookie数据,步入
调用readvalue方法,就是获取cookie中rememberMe字段的值
回到getRememberedPrincipals方法,进入base64解密,将数据传回,回到convertBytesToPrincipals方法
调用convertBytesToPrincipals方法,跟进
先进行AES解密,然后反序列化,便执行了我们的恶意代码
漏洞复现
感觉自己搭建的环境有点问题,始终执行不了命令,所以后续选择了vulhub上的环境,这上面的环境是以CommonsBeanutils1作为攻击链的
脚本如下
1 | import base64 |
将生成的cookie放入即可执行命令