Shiro反序列化漏洞-shiro550流程分析
Shiro反序列化漏洞-shiro550流程分析
前言
今天开启Shiro反序列化漏洞的篇章,Shiro-550漏洞原因简单来说就是固定Key加密。Shriro算是前几年最好用的RCE漏洞之一,原因有很多:Shiro框架使用广泛、漏洞影响范围广;攻击payload经过AES加密等等。
Shiro550漏洞原理简介
Apache Shiro框架提供了记住我的功能(RememberMe),用户登陆成功后会生成经过加密并编码的cookie(序列化→AES加密→Base64编码),在服务端接收cookie值后,Base64解码–>AES解密–>反序列化。攻击者只要找到AES加密的密钥,就可以构造一个恶意对象,对其进行序列化–>AES加密–>Base64编码,然后将其作为cookie的rememberMe字段发送,Shiro将rememberMe进行解密并且反序列化,最终造成反序列化漏洞
关键因素:AES的加密密钥在Shiro的1.2.4之前版本中使用的是硬编码:kPH+bIxk5D2deZiIxcaaaA==,只要找到密钥后就可以通过构造恶意的序列化对象进行编码,加密,然后作为Cookie加密发送,服务端接收后会解密并触发反序列化漏洞。在1.2.4之后,ASE秘钥就不为默认了,需要获取到Key才可以进行渗透。
漏洞复现
可以使用 P神写的 demo:https://github.com/phith0n/JavaThings
打开项目添加配置启动Tomcat,运行就得到了1.2.4版本的环境。

输入账号密码,勾选Remember me字段,可以看到在返回包中有Set-Cookie:rememberMe=deleteMe字段,会返回一个Cookie,之后的所有请求中 Cookie 都会有 rememberMe 字段,那么就可以利用这个 rememberMe 进行反序列化,从而 getshell

我们接下来看一下这个Cookie是怎么处理的,处理的这个类是CookieRememberMeManager,里面有rememberSerializedIdentity方法和getRememberedSerializedIdentity方法,分别对应序列化的操作和反序列化的操作 ,简单看一下rememberSerializedIdentity是怎么判断的
先判断是否为 HTTP 请求,接着获取 cookie 中 rememberMe 的值,然后判断是否是 deleteMe,不是则判断是否是符合 base64 的编码长度,然后再对其进行 base64 解码,将解码结果返回。我们就想,base64解码后的下一步是什么,回头找一下哪里调用了这个函数。
我们找到AbstractRememberMeManager类的getRememberedPrincipals函数,获取到base64解码的数据过后,进了convertBytesToPrincipals这个函数
convertBytesToPrincipals函数里就做了两步:1、解密 2、反序列化 这里我们就大概能知道它的原理,它最后肯定会进行一个反序列化操作,但这个字节是加密过的,所以我们需要先把它解密
先来看一下它反序列化的地方,这里实际上就是调用了原生的反序列化。这里比方说它有CC1的依赖,这里就可以打漏洞。
接着跟进一下decrypt函数,看看解密的方法。根据名字可以看到进行了先获取密钥的服务,判断不为空后解密,
继续根据decrypt,它是个接口,看一下它的参数名:第一个叫encrypted,也就是加密的字段,第二个就叫decryptionKey,也就是说它应该是一个对称加密,用key去解密。那我们就可以重点区想一下它的key是什么东西,因为如果能获取这个key的话就能重新构造这个包了
回到解密这里,发现它的Key值是通过函数去获取的
跟进这个函数,发现这里是个常量
继续跟进,看一下这个decryptionCipherKey是在哪里赋值的,发现是在setDecryptionCipherKey这个函数中操作的
一路跟进
发现是DEFAULT_CIPHER_KEY_BYTES这个常量
终于找到了,可以看到这就是一个固定的值 kPH+bIxk5D2deZiIxcaaaA==
也就是说Shiro1.2.4这个版本,所有跟RememberMe相关的加密采用的是一个固定的key值加密,它的算法就是AES算法,这里AES也是一个固定的算法。
利用的方式也很清晰:我们首先构造一个序列化的payload,然后把它用AES的key加密,再用base64加密,最后想办法让它走到正常的流程里面进行反序列化。
那首先想的是,payload用什么,打它的什么库。帖子里一般打得都是CC,因为Shiro里面看好像是自带依赖里面也是有CC。但实际上通过Maven Helper插件看,很多包都是test字段,就是说编译Maven真的运行的时候只会把compile和runtime打进来,test是不会打进来,像这个CC都是test里面的,也就是说实际上网站去打CC是打不到的。
今天主要演示原理,打jdk自己的也就是URLDNS这条链子,这条链子已经很熟悉了
1 | |
编译完后生成的文件再通过AES算法加密一下
1 | |
得到加密后的字段
然后我们就可以在抓包,并且在后续的请求中替换掉Cookie中的RememberMe字段了,有一点需要注意:存在SessionID会判断SessionID,走不到RememberMe,所以我们要再请求包中删除掉SessionId字段。接着发送请求,Dnslog就可以收到请求了,证明这里成功调用了反序列化
前面都是静态的查看,我们下个断点发现确实走到了Shiro的readObject函数中,下一步就是到了HashMap的readObject
这也证明了Shiro的流程,原理相对是比较简单清晰的。
快速检测Shiro漏洞
结合挖洞经验,补充一下快速检测
快速检测key值是否正确的方式:
1.构造一个继承 PrincipalCollection 的序列化对象。
2.key正确情况下不返回 deleteMe ,key错误情况下返回 deleteMe 。
如何判断存在shiro?:在 request 的 cookie 中写入 rememberMe=1 ,然后再来看 response 的 set-cookie 是否出现的 rememberMe=deleteMe
工具扫描:BurpShiroPassiveScan
后记
Shiro这个漏洞它的特点就是传递的是反序列化的数据,但是它天然的进行了加密,也就是说它不会有反序列化的标志,比如说天然的纯反序列化base64加密后是aced开头或者rO0AB开头。并且Shiro天然加密,每次的iv值也不一样,所以它是没有特征的,它只会传一个加密的字符串,一般的WAF也不好检测,但是目前为止我们只是发起一个DNS请求,进行简单地一个漏洞验证,但具体RCE的实现就要看打什么依赖,一般来说Shiro和Springboot漏洞可以结合使用,有时也会带上CC,后续的篇章会写Shiro打不同依赖的方法。