shiro系列漏洞探索
分类
按照利用难度和危害我们依次分为
shiro550 > shiro721 > shiro权限绕过
为什么叫SHIRO-550 呢?
https://issues.apache.org/jira/browse/SHIRO-550
shiro550漏洞分析
shiro-core-1.2.4.jar!/org.apache.shiro.mgt.AbstractRememberMeManager.class#RememberMeManager
AbstractRememberMeManager 继承了RememberMeManager,看到了硬编码的key
第一个断点打在
org.apache.shiro.mgt.AbstractRememberMeManager.class#getRememberedPrincipals
然后点击step over
进行跟踪
当cookie的base64解密后不等于deleteMe
的进入循环,把base64.decode后的值赋值给数组类型的decoded
这边的decode是shiro自定义函数
接着往下走, principals = this.convertBytesToPrincipals(bytes, subjectContext);
点击step into跟进convertBytesToPrincipals, 最后会执行deserialize反序列化输入的字节,至此触发了反序列化漏洞
那shiro自定义的decode函数内部是如何实现的呢?网上说的AES加解密又是怎么回事?
我们这边进上边的this.decrypt函数,getDecryptionCipherKey的值是默认的
就是private byte[] decryptionCipherKey;也就是我们常见的默认key: kPH+bIxk5D2deZiIxcaaaA==
继续进行跟进 cipherService.decrypt
org.apache.shiro.crypto.JcaCipherService.class#ByteSource decrypt
iv 不为空,cbc解密,这样就比较奇怪了,我们生成cookie的时候并没有输入iv偏移量,继续跟踪
发现cbc解密的时候iv 为空
函数System.arraycopy(ciphertext, ivByteSize, encrypted, 0, encryptedSize)中iv是
[-5, -30, 5, 65, -34, 25, 34, -11, -102, -125, 98, -56, 73, 25, -120, 114],经过多次测试发现是一个变化的值,那cookie是怎么解密的呢?
在183行System.arraycopy以前,这时候的iv值还是空值
第183行中System.arraycopy(ciphertext, 0, iv, 0, ivByteSize);函数进行了iv的赋值。这个iv也就是cookie中的iv变量的值
我们对现在ciphertext函数中的iv值
{75, -17, -35, 46, -2, 49, 73, -35, -97, 115, -13, 61, 80, -54, 47, 111 }进行解密得到K��.�1I��s�=P�/o
对生成利用脚本中生成的cookie值K��.�1I��s�=P�/o iv 做比较,发现一致,证明这个iv就是我们攻击payload中cookie的iv值,也就是说这个iv是我们前段可以控制的。
最后调用
org.apache.shiro.mgt.AbstractRememberMeManager.class#PrincipalCollection deserialize(byte[] serializedIdentity)
触发反序列化(也就是说shiro重写了readObject)
org.apache.shiro.crypto.JcaCipherService.class#DefaultSerializer.class
其他代码问题
1 shiro是从哪里获取传入的cookie值
1.2.4/shiro-web-1.2.4.jar!/org/apache/shiro/web/mgt/CookieRememberMeManager.class
byte[] decoded = Base64.decode(base64)
cookie 的所有值
2 rememberMe中的cookie加密的AES是哪种模式?
org.apache.shiro.web.mgt.CookieRememberMeManager.class#getRememberedSerializedIdentity函数中
String base64 = this.getCookie().readValue(request, response)
3 断点应该怎么打?直接在触发点T deserialized = ois.readObject()处打,就可以看到所有的利用链了,这也是反序列化漏洞审计的一个通用方法,因为触发点都在readObject()函数
/org/apache/shiro/shiro-core/1.2.4/shiro-core-1.2.4.jar!/org.apache.shiro.io.DefaultSerializer.class#deserialize
点击Copy Stack 复制调用链
4 shiro的本身的路由是如何进行的
/org/apache/shiro/shiro-web/1.2.4/shiro-web-1.2.4.jar!/org/apache/shiro/web/servlet/OncePerRequestFilter.class
相关请求
函数调用链
deserialize:77, DefaultSerializer (org.apache.shiro.io)
deserialize:514, AbstractRememberMeManager (org.apache.shiro.mgt)
convertBytesToPrincipals:431, AbstractRememberMeManager (org.apache.shiro.mgt)
getRememberedPrincipals:396, AbstractRememberMeManager (org.apache.shiro.mgt)
getRememberedIdentity:604, DefaultSecurityManager (org.apache.shiro.mgt)
resolvePrincipals:492, DefaultSecurityManager (org.apache.shiro.mgt)
后续
orange 说使用cc3链利用不成功?
Pwn a CTF Platform with Java JRMP Gadget中评论说到
发现原本的 ObjectInputStream.resolveClass() 是调用的 Class.forName() 来加载类。shiro这边的ObjectInputStream.resolveClass()是调用的loadClass
org.apache.shiro.util.ClassUtils.class#loadClass
这边参考voidfoo子航师傅文章了解到:
所以这里就涉及到一个问题,ClassLoader.loadClass() 和 Class.forName() 在加载类时有什么区别?
Class.forName("SomeClass") 会进行类初始化(执行 static 代码块),ClassLoader.loadClass("SomeClass") 则不会
但这个区别不是这里 commons-collections gadget 在 shiro 反序列化漏洞中无法直接利用的原因,这里直接利用会失败的真正原因是 ClassLoader.loadClass() 不支持加载数组类型的类,Class.forName() 才支持。
如果目标机器不出网怎么检测key
这边主要是参考了一种另类的 shiro 检测方式文章
不出网利用
1.jrmp延时
2.获取前台js文件在相同下路径写shell
3.内存shell
实战中的一点思考
shiro和fastjson负载问题
因为有些负载主机的shiro.jar 包没有升级,导致可以继续被利用,这种情况下可以多尝试几遍攻击,来尝试流量经过未被加固过的主机
路径回现
参考
voidfoo大佬wiki文章
一种另类的 shiro 检测方式
漏洞修复
Apache Shiro Java 反序列化漏洞分析
本作品采用 知识共享署名 -非商业性使用 -相同方式共享 4.0 国际许可协议 进行许可。