前言
shiro反序列化一直在打,原理大致了解为由于硬编码加解密了cookie中rememberMe字段的内容,在shiro处理时解密了rememberMe字段的内容并进行了反序列话,在存在合适的gadget时可以执行任意命令。本文并无创新点,为自己学习老洞的过程记录。
反序列化入口点分析
首先确定思路,思路如下:
1、确认正常情况下,一段明文是如何加密为密文的。加密方式是什么,密钥是什么。
2、在数据包携带rememberMe=xxx时,shiro是如何处理rememberMe字段中的内容的。
shiro环境搭建:https://blog.csdn.net/god_zzz/article/details/108391075
搭建成功后如下
AbstractRememberMeManager.onSuccessfulLogin
onSuccessfulLogin为登录成功时调用的方法。在297行打上断点,通过登录shiro来触发。
输入账号密码:root/secret,勾选Remember Me,然后点击login,触发断点。
进入rememberIdentity方法
AbstractRememberMeManager.rememberIdentity
AbstractRememberMeManager.rememberIdentity
shiro在convertPrincipalsToBytes方法中对明文进行了序列化和加密,跟入其中看看究竟。
AbstractRememberMeManager.convertPrincipalsToBytes
首先是序列化,调用了serialize对明文字符串root进行了序列化,将其转换为bytes数组。然后在getCipherService()方法不为空的情况下,调用encrypt方法对bytes数组进行加密。
可以看到直接返回了一个AesCipherService的实例。看一下构造函数中有没有指示cipherService这个值是从哪里来的。
AbstractRememberMeManager.AbstractRememberMeManager
可以看到构造函数中new了一个AesCipherService的实例,并为其设置key,值为DEFAULT_CIPHER_KEY_BYTES。也就是kPH+bIxk5D2deZiIxcaaaA==,这里就是一个硬编码的密钥。
到此为至,思路1我们以及弄清楚了。shiro对明文加密方式为AES加密,密钥为硬编码写到代码中的。
思路1弄清楚了之后我们也不着急推出,继续看看这个bytes是怎么和cookie建立关系的。我们在AbstractRememberMeManager.rememberIdentity中的347行设置断点,然后继续f9将代码运行到该断点处。
进入rememberSerializedIdentity(subject, bytes)方法中
CookieRememberMeManager.rememberSerializedIdentity
我们看到被加密的字符串在152行被进行了Base64的编码,然后将其值赋给了cookie中的rememberMe字段
体现在数据包上就是如下图所示:
关于思路1的跟踪到此为止。
接下来分析,在数据包携带rememberMe=xxx时,shiro是如何处理rememberMe字段中的内容的。
关于上面这个问题,我的思路为,之前加密时调用了AbstractRememberMeManager的encrypt方法。那么在处理解密时应当也会调用该类的decrypt方法。查看源码中哪些类调用了这个类的decrypt方法。
在convertBytesToPrincipals方法中调用了decrypt
在getRememberedPrincipals方法中调用了convertBytesToPrincipals
DefaultSecurityManager的getRememberedIdentity方法中调用了getRememberedPrincipals方法
DefaultSecurityManager的resolvePrincipals方法调用了getRememberedIdentity方法
DefaultSecurityManager的createSubject方法调用了resolvePrincipals方法
到这里再继续向上面找就不好找了,不慌,我们断点调试。直接把断点打在DefaultSecurityManager的342行,发送数据包,并给数据包添加一个cookie,rememberMe=111。
1 | POST /samples_web__1___org_apache_shiro_samples__war_exploded/login.jsp;jsessionid=5B028884FEC703B1EF35FE51564C1D63 HTTP/1.1 |
发送数据包后,断点停在DefaultSecurityManager的342行
我们发现,这个调用是从一个shiro的filter开始的,最后走到DefaultSecurityManager的342行。那么我们f7从该断点进入方法,看具体执行流程是否和我们预期一样走到解密、反序列化那一块。逐步跟进后发现是走到decrypt那里的。具体就不截图了。其中看两个重点部分,一个是shiro是怎么从cookie中取出rememberMe值的,一个是最后反序列化的位置。
跟入getRememberedSerializedIdentity方法
发现就是直接取了rememberMe的值,然后base64解码。
解密后,反序列化,这样就找到了shiro反序列化的成因。
然后就是挖掘gadget部分,两者合并才能达成一个反序列化漏洞的利用。
CC6
使用CC6的gadget在ClassUtil.forName打断点调试,我们发现数组类无法被loadClass加载,因此cc6的链无法在这里使用。不止cc6,依赖于cc3.2jar包的cc链都使用了Transformer[]这个数组。因此我们无法使用shiro自带的ccjar包。不过要是使用了shiro的工程引用了cc4jar包,那么我们就可以使用cc2的链和cc4的链去命令执行。
利用脚本(同目录放置ysoserial-0.0.6-SNAPSHOT-BETA-all.jar)
1 | import base64 |
关于shiro使用cc3 jar包的调用链
参考:https://www.anquanke.com/post/id/192619
改造一下yso项目的cc6 exp即可。
shiro突破header头长度限制
通过Template可以加载任意字节码特性,把evilCode放在request参数中。
参考:https://mp.weixin.qq.com/s/5iYyRGnlOEEIJmW1DqAeXw
总结
原理还是比较简单粗暴的,cookie内的硬编码加密的数据直接进行了反序列化和cas的反序列化有点像。难点在于如何挖掘出类似漏洞,前段时间研究的CodeQL可能能挖掘出类似的漏洞,通过CodeQL找到sink和source,列出可疑点然后进行分析。