前言
陆陆续续分析了几个Java的安全漏洞,接下来轮到shiro的了。这里分析的是Shiro-550,硬编码rememberMe密钥造成的反序列化漏洞。分析起来比想象着的简单。
环境搭建
Shiro-550是使用shiro1.2.4及以下的默认密钥导致的反序列化漏洞,环境的代码我是用的github上一个大佬写的,使用的是Maven包管理,不用怎么搭,自动下载依赖直接运行即可。
漏洞调试
根据网上大部分的复现文章,使用ysoserial生成CommonsBeanutils1
反序列化链的payload,并使用上面shiro漏洞环境项目的GenPayload
类加密一下payload,生成shiro可识别的rememberMe内容。
然后依旧是在Runtime
的exec
方法下断点,Cookie中设置rememberMe并发送。调用栈挺长的,但跟rememberMe有关的是从AbstractRememberMeManager
开始这段。
跟进到AbstractRememberMeManager
的getRememberedPrincipals
方法。看到bytes
变量的内容为加密后的序列化链,跟进getRememberedSerializedIdentity
方法看看是如何获取rememberMe内容的。
getRememberedSerializedIdentity
方法在AbstractRememberMeManager
类中是抽象方法,具体的实现代码要去到AbstractRememberMeManager
的子类CookieRememberMeManager
中看。整体就是从Cookie中获取rememberMe内容,并返回base64解码的内容。rememberMe的内容是通过this.getCookie()
获取当前的cookie
对象,而cookie
初始化时是用rememberMe
这个名称的,获取到的内容相应的也该名称的值。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| protected byte[] getRememberedSerializedIdentity(SubjectContext subjectContext) { if (!WebUtils.isHttp(subjectContext)) { ... return null; } else { WebSubjectContext wsc = (WebSubjectContext)subjectContext; if (this.isIdentityRemoved(wsc)) { return null; } else { HttpServletRequest request = WebUtils.getHttpRequest(wsc); HttpServletResponse response = WebUtils.getHttpResponse(wsc); String base64 = this.getCookie().readValue(request, response); if ("deleteMe".equals(base64)) { return null; } else if (base64 != null) { base64 = this.ensurePadding(base64); ... byte[] decoded = Base64.decode(base64); ... return decoded; } else { return null; } } } }
|
获取到字节数组为非空的话会继续调用convertBytesToPrincipals
方法,在convertBytesToPrincipals
方法中先调用decrypt
方法对rememberMe进行解密。shiro使用的是AES加密,按理说还具有初始化向量iv
,但解密时候的iv
是从rememberMe的开头获取的,所以不影响payload的构造。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| public ByteSource decrypt(byte[] ciphertext, byte[] key) throws CryptoException { byte[] encrypted = ciphertext; byte[] iv = null; if (this.isGenerateInitializationVectors(false)) { try { int ivSize = this.getInitializationVectorSize(); int ivByteSize = ivSize / 8; iv = new byte[ivByteSize]; System.arraycopy(ciphertext, 0, iv, 0, ivByteSize); int encryptedSize = ciphertext.length - ivByteSize; encrypted = new byte[encryptedSize]; System.arraycopy(ciphertext, ivByteSize, encrypted, 0, encryptedSize); } catch (Exception var8) { String msg = "Unable to correctly extract the Initialization Vector or ciphertext."; throw new CryptoException(msg, var8); } }
return this.decrypt(encrypted, key, iv); }
|
接着是对解密后的内容进行反序列化,最终调用方的是DefaultSerializer
类的deserialize
方法。这里实例化ObjectInputStream
类是用他的子类ClassResolvingObjectInputStream
是实现的。
ClassResolvingObjectInputStream
的resolveClass
方法是通过forName
方法返回Class实例的,但forName
不支持数组类型的Class,所以反序列化的利用链没使用CommonsCollections
的,而是用了CommonsBeanutils1
。因为事先看了别的师傅复现的文章,所以没有踩这个坑。具体的forName
原理在@zsx师傅的文章里有详尽的分析。除此之外,后面的反序列化就与普通的反序列化无异。
后记
CommonsBeanutils1
里的TemplatesImpl
在上回Java反序列化的文章里没有学到,好像fastjson不出网的payload是有用到这个的,后续还还要补充学习。冲冲冲
参考
https://www.cnblogs.com/loong-hon/p/10619616.html
https://p2hm1n.com/2020/12/03/Shiro550-%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%E6%BC%8F%E6%B4%9E%E5%88%86%E6%9E%90/
https://www.mi1k7ea.com/2020/10/03/%E6%B5%85%E6%9E%90Shiro-rememberMe%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%E6%BC%8F%E6%B4%9E%EF%BC%88Shiro550%EF%BC%89/#%E9%83%A8%E5%88%86Gadget%E6%89%93%E5%A4%B1%E8%B4%A5%E7%9A%84%E5%9D%91
http://www.lmxspace.com/2019/10/17/Shiro-%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%E8%AE%B0%E5%BD%95/#4-%E4%BF%AE%E5%A4%8D%E6%96%B9%E5%BC%8F
https://blog.zsxsoft.com/post/35