一个”记住我”的贴心功能,因为一个硬编码的默认密钥,把全球无数 Java 应用的认证大门彻底打开。
攻击者不需要破解密码,只需要”知道那个密钥”——而那个密钥,早就公开在了互联网上。
引言:框架的”RememberMe”之罪
在 Java Web 开发中,Apache Shiro 是最流行的安全框架之一,负责认证、授权、会话管理。几乎每个”会做登录”的 Java 项目,都可能用到它。
而 Shiro 的反序列化漏洞,却来自一个最”贴心”的功能——“记住我(RememberMe)”:
Shiro 把用户身份序列化后加密,存进 Cookie。而这个加密用的 AES 密钥,早期版本是硬编码的默认值——并且早已被公开。
一句话定义它:
Shiro 的反序列化漏洞 = “默认密钥” + “自动反序列化 Cookie”,让攻击者可以伪造任意身份、执行任意代码。
一、rememberMe 机制:贴心功能如何变成后门?
1.1 “记住我”的正常流程
当用户勾选”记住我”登录后,Shiro 会:
1 | 登录成功 |
1.2 两个致命问题
这个流程看似安全(有 AES 加密),但存在两个致命漏洞:
- 默认密钥:早期 Shiro 的 AES 密钥是硬编码的默认值
kPH+bIxk5D2deZiIxcaaaA==,且早已公开。 - 自动反序列化:Shiro 解密 Cookie 后,会自动反序列化其中的对象。
1 | 攻击者知道默认 AES 密钥 |
关键点:攻击者不需要”破解”密钥,因为密钥本来就是公开的默认值。 这使 Shiro 的反序列化漏洞的利用门槛极低。
二、漏洞详情:CVE-2016-4437
2.1 漏洞概述
- CVE 编号:CVE-2016-4437
- 漏洞类型:默认 AES 密钥导致的 RememberMe 反序列化 RCE
- 影响版本:Apache Shiro ≤ 1.2.4(默认密钥未更换时)
- 利用前提:目标使用了默认的硬编码 AES 密钥
2.2 默认密钥的”公开化”
Shiro 早期版本中,加密密钥硬编码在源码里:
1 | // Shiro 早期版本的默认密钥(已被公开,脱敏处理) |
这个密钥在所有使用默认配置的 Shiro 应用中完全相同,且早已在互联网上广泛流传。
三、代码级攻击链:从 ysoserial 到 Shiro RCE
下面还原一条完整的 Shiro 攻击链(脱敏,仅供安全教育/授权测试):
3.1 第一步:生成恶意序列化对象
1 | # 用 ysoserial 生成 Commons Collections 恶意序列化对象 |
3.2 第二步:用默认密钥 AES 加密
1 | # 概念示例:用 Shiro 默认密钥加密 payload(脱敏,仅供安全教育) |
3.3 第三步:Base64 编码后放入 Cookie
1 | # 概念示例:构造 rememberMe Cookie 发送给目标(脱敏) |
3.4 第四步:获得 RCE
1 | Shiro 读取 rememberMe Cookie |
四、Shiro 漏洞的”变种”与”进阶”
4.1 无默认密钥时的密钥爆破
即使目标更换了默认密钥,攻击者仍有手段:
- Shiro 的 rememberMe Cookie 前有一段固定的前缀(编码后可见)。
- 攻击者利用这个特征,可以在线爆破 AES 密钥(字典攻击)。
1 | 攻击者收集目标的 rememberMe Cookie |
4.2 不同 gadget 链的适配
Shiro 的反序列化能否利用,取决于目标 classpath 里的 gadget 库:
| gadget 链 | 适用环境 |
|---|---|
| Commons Collections | 老项目常见 |
| Commons Beanutils | 需配合特定库 |
| 无依赖链(基于 JDK) | 特定 JDK 版本 |
攻击者会先用探测判断目标是否存在 Shiro 反序列化点,再选择对应的 gadget 链。
五、Shiro 反序列化的特征与检测
5.1 识别 Shiro 应用
1 | # 概念示例:通过 Cookie 特征识别 Shiro(脱敏) |
5.2 检测维度
| 检测点 | 方法 |
|---|---|
响应中出现 rememberMe=deleteMe |
指纹识别 |
| 请求 Cookie 中 rememberMe 值异常(超大、可解密) | 流量分析 |
异常的 readObject / AES 解密调用 |
RASP |
| 出站到反弹 shell 的连接 | EDR / 网络监控 |
5.3 一个实用的检测命令
1 | # 概念示例:检索 Web 日志中异常的 rememberMe 请求(脱敏) |
六、防御:如何堵住 Shiro 的”默认密钥”漏洞?
6.1 核心防御(必须做)
- 更换默认 AES 密钥:
1 | // 使用强随机密钥替换默认密钥(脱敏示例) |
- 升级 Shiro 版本:升级到修复版本,并使用新的加密机制。
- 完整性校验(HMAC):对 Cookie 增加 HMAC 签名,防止伪造。
6.2 纵深防御
- 运行时防护(RASP):监控并阻断恶意反序列化。
- 依赖最小化:移除不需要的 gadget 库。
- 最小权限:应用以低权限运行,限制 RCE 后的杀伤面。
- 网络监控:监控异常的出站连接(反弹 shell)。
6.3 密钥管理的启示
Shiro 的教训是通用的:任何”硬编码的密钥”,无论是默认密码、默认 token、还是默认 API key,都等同于”公开的密钥”。 密钥管理的第一原则,是”每个部署实例使用独立、随机、受保护的密钥”。
六·补:一个真实世界的「Shiro 失守」复盘
光讲原理太抽象,我们把 Shiro 漏洞放到真实场景里走一遍,理解它为什么会成为「渗透测试里最常见的入口之一」:
- 发现目标:攻击者在信息收集阶段,随手访问目标站点,发现响应头里
Set-Cookie: rememberMe=deleteMe——这是 Shiro 未登录时的标志性特征,等于「自报家门」。 - 判断密钥:攻击者用 Shiro 默认密钥
kPH+bIxk5D2deZiIxcaaaA==尝试解密自己构造的 Cookie,如果「签名校验通过」,说明目标还在用默认密钥。 - 选 gadget 链:探测目标 classpath 里有哪些库——有 Commons Collections 就用 CC 链,有 Commons Beanutils 就用 CB 链,什么依赖都没有就试 JDK 自带的链。
- 生成 payload:用 ysoserial 生成对应的恶意序列化对象,用默认密钥 AES 加密、Base64 编码,塞进
rememberMeCookie。 - 拿到 RCE:请求一发,Shiro 解密、反序列化、触发 gadget,反弹 shell 到手。
这条链之所以「屡试不爽」,根源在于三个「默认」的叠加:默认密钥(没换)、默认机制(rememberMe 自动反序列化)、默认依赖(classpath 里的 gadget 库)。任何一个「默认」被打破,攻击就难一个数量级——这恰恰是防御的切入点。
为什么「换个密钥」并不能一劳永逸
很多开发者以为「把默认密钥换掉就安全了」,这是一个危险的错觉。原因有三:
- 密钥爆破依然可能:Shiro 的 rememberMe Cookie 有固定的明文特征(前缀),攻击者可以据此做「已知明文攻击」,从弱密钥字典里爆破出你的自定义密钥——如果密钥不够强、不够随机,照样被破。
- 反序列化本身的缺陷仍在:即使密钥固若金汤,只要 rememberMe 还走「解密 → 反序列化」,而 classpath 里还有 gadget 库,攻击者一旦拿到密钥,威胁依旧存在。
- 根本问题在「自动反序列化」:真正该做的,是从协议层面杜绝「反序列化不可信数据」,而不是只在「加密」这一层打补丁。
所以正确的防御是「三层叠加」:换强随机密钥(治标)+ 升级版本/关闭 rememberMe 反序列化(治本)+ 移除多余 gadget 库(减少攻击面)。
七、结语:默认值,是安全的大忌
Shiro 的反序列化漏洞,技术原理并不复杂,却影响了全球无数 Java 应用,根源只有一个:
一个硬编码的”默认密钥”,让”记住我”这个贴心功能,变成了攻击者的”万能钥匙”。
对开发者而言,Shiro 最深刻的教训是:安全系统的每一个”默认值”,都必须假设它已经被公开。 默认密码要改、默认密钥要换、默认配置要审查——因为攻击者,永远比你想的更早拿到这些”默认值”。
参考资料
- Apache Shiro 关于 CVE-2016-4437 的官方公告
- 关于 Shiro 默认密钥、密钥爆破的公开研究
- ysoserial 项目及 Shiro 反序列化利用工具
本文基于公开资料整理撰写,仅供网络安全研究与教育目的使用,不构成任何安全或法律建议。
相关文章
本文是「Java 反序列化攻击面」系列的第三篇:
- 《Java 反序列化攻击面(一):WebLogic 的 T3 协议与”漏洞打地鼠”史》 —— 系列第一篇
- 《Java 反序列化攻击面(二):Fastjson 的 autoType 与”修复 vs 绕过”拉锯战》 —— 系列第二篇
- 《Log4Shell:一次震动全球 Java 生态的”完美漏洞”》 —— Java 生态的另一高危漏洞
- 《CVE-2025-24813 Tomcat PUT漏洞POC——反序列化RCE的完整利用链》 —— Java 中间件的漏洞全景
评论