Apache Shiro RememberMe 反序列化:从原理到告警研判证据链

前言:研判 Shiro 告警要回答的五个问题

Shiro 反序列化是 Java Web 安全中非常典型的告警类型。刚接触时容易把几个概念混在一起:

  • 响应出现 rememberMe=deleteMe,是不是已经攻击成功?
  • Cookie 中出现很长的 rememberMe,是不是一定是恶意 Payload?
  • Shiro-550 和 Shiro-721 都是反序列化,到底有什么区别?
  • 安全设备命中”Shiro 反序列化”后,应该看响应包,还是查服务器?
  • 查到旧版本、默认密钥或 Gadget 特征,能不能直接定性为 RCE 成功?

核心结论先给出:

Shiro 框架指纹、受影响版本、恶意反序列化对象和命令执行结果,是四个不同层次的证据。只有把流量证据、应用证据和主机证据关联起来,才能判断风险是否真正落地。

一、RememberMe 为什么会和反序列化扯上关系

Apache Shiro 是 Java 生态的安全框架,提供认证、授权、会话管理和 RememberMe(记住我)能力。

传统 RememberMe 机制的写入流程:

1
2
3
4
5
6
7
8
9
10
11
登录成功并勾选"记住我"
↓
收集 PrincipalCollection 身份信息
↓
Java 序列化为字节数据
↓
AES 加密
↓
Base64 编码
↓
写入 rememberMe Cookie

浏览器后续访问时流程反过来:Base64 解码 → AES 解密 → Java 反序列化 → 恢复身份对象。

危险点在于:**rememberMe 来自客户端,是外部可控数据**。只要攻击者能构造出服务端可正确解密的数据,且解密结果是恶意 Java 序列化对象、目标依赖里存在可利用的 Gadget Chain,就可能在反序列化过程中触发非预期代码执行。

一条完整利用链需要同时满足:

  1. 目标启用了 RememberMe 处理;
  2. 目标处于受影响版本或存在等价的不安全实现;
  3. 攻击者能生成服务端可接受的密文;
  4. 服务端类路径存在匹配的 Gadget Chain;
  5. 恶意对象确实进入反序列化流程;
  6. 最终操作未被 Java 版本、权限、容器、EDR 等阻断。

所以:发现 Shiro ≠ 存在漏洞;存在旧版本 ≠ 一定可利用;出现恶意 Cookie ≠ 命令已执行。

二、Shiro-550 vs Shiro-721

对比项 Shiro-550 Shiro-721
对应 CVE CVE-2016-4437 CVE-2019-12422
NVD CVSS v3.1 9.8 Critical 7.5 High
影响范围 Shiro ≤ 1.2.4(修复起 1.2.5) Shiro ≤ 1.4.1(修复起 1.4.2)
主要突破点 已知或泄露的密钥 AES-CBC Padding Oracle
是否必须知道密钥 通常需要 不一定
是否需要合法 Cookie 不一定 通常需要
利用交互次数 相对较少 需要多次请求

一句话区分:

**Shiro-550 是”门锁钥匙被公开”**——旧版本存在公开固定的默认 RememberMe 密钥(kPH+bIxk5D2deZiIxcaaaA==),攻击者知道密钥就能加密恶意序列化对象。

**Shiro-721 是”不知道钥匙,但可以根据门锁对不同输入的反馈逐步推导有效密文”**——利用 CBC 模式和服务端响应差异做 Padding Oracle 攻击。

一个容易被忽略的点:升级到 1.2.5 只是解决了已知默认密钥。如果开发后来又配置了从公开示例复制的固定密钥、多系统共用的弱密钥、可从配置/镜像/仓库泄露的密钥、或长期不轮换的硬编码密钥,相似风险会重新出现。**”不是默认密钥”不代表密钥管理安全。**

三、研判第一步:rememberMe=deleteMe 是什么

探测 Shiro 的常见方式:

1
Cookie: rememberMe=1

如果目标由 Shiro 处理,响应可能出现:

1
Set-Cookie: rememberMe=deleteMe; Path=/; Max-Age=0

它能说明什么:请求到达了处理 RememberMe 的组件;目标很可能用了 Shiro;如果由陌生外部源发起,可能处于框架探测阶段。

它不能说明什么:不能说明是受影响版本、用了默认密钥、攻击者已找到正确密钥、恶意对象已反序列化、命令已执行或主机已失陷。

误报来源:用户保存了失效的旧 Cookie、应用更新或密钥轮换、多节点密钥配置不一致、Cookie 被代理截断污染、自动化测试携带异常 Cookie、安全设备只按 rememberMe 长度或关键字匹配。

rememberMe=deleteMe 是框架指纹和探测线索,不是漏洞利用成功的标志。

正常与恶意 Cookie 外观上都是:长、Base64 字符、肉眼不可读。只按”Cookie 很长”告警会产生大量误报。

区分方法(在已授权的离线环境用实际密钥解密分析):

正常身份数据的解密结果指向 Shiro 身份对象:PrincipalCollection、Realm 名称、用户名/用户 ID 等业务身份结构。

恶意对象的解密结果出现这些特征时风险等级立即上调:

  • ysoserial 等Payload 生成工具痕迹
  • TemplatesImpl、BeanComparator 等高风险 Gadget 类
  • Runtime.getRuntime().exec、ProcessBuilder 等进程创建路径
  • whoami、cmd.exe、curl、wget 等命令字符串
  • DNS/HTTP/LDAP/RMI 外联地址
  • 与身份对象完全无关的复杂对象图

但仍要区分两个结论:

  1. 解密后发现恶意对象结构 → 证明有明确攻击意图,且攻击者可能掌握有效构造条件;
  2. 恶意对象已执行成功 → 还需要响应、进程、文件、网络或日志证据。

Gadget 不匹配、类不存在、Java 版本不兼容或被安全控制阻断时,Payload 再漂亮也不会形成实际命令执行。

实操红线:不要把生产环境 Cookie 上传到在线解密网站,解密结果可能包含用户身份、Realm/类名/技术栈、内部业务字段甚至认证材料。现场用经过审批的本地离线工具;只做字节和结构分析,不要通过 ObjectInputStream.readObject() 或加载 Gadget 类来“查看”未知载荷,避免分析过程触发执行。

五、研判第三步:攻击到底有没有成功

有回显场景

响应中出现与测试命令严格对应的内容(当前用户名、主机名、唯一随机标记等)是强成功证据。但要排除:页面本来就含同样字符串、网关/扫描器回显了请求内容、响应属于缓存或其他请求。用请求 ID、五元组、时间戳和唯一标记确认请求与响应的对应关系。

无回显场景(更常见)

Java 反序列化经常没有 HTTP 回显。不能因为”响应正常”就判断攻击失败,继续查四类主机证据:

证据类型 排查点
进程 Java 进程是否创建 cmd.exe/powershell/curl/wget 子进程;父子关系与命令行参数是否与 Payload 一致;时间是否吻合
文件 Web/临时/上传目录新文件;脚本、WebShell、计划任务、异常 JAR;哈希是否命中情报
网络 Java 进程异常 DNS 查询;向陌生 IP/域名的 HTTP/LDAP/RMI 连接;第二阶段载荷下载;反连/横向
日志 应用日志的 ClassNotFoundException、BadPaddingException 等解密/反序列化异常;EDR/HIDS 的 Java 子进程记录

HTTP 状态码不能直接定性

现象 正确理解
HTTP 200 正常返回页面,不代表命令执行成功
HTTP 302 可能只是跳登录页
HTTP 500 可能是 Payload 触发异常,也可能是普通应用错误
rememberMe=deleteMe Cookie 被判无效或处理失败
响应变慢 可能与测试行为有关,也受负载影响
DNS 外联命中唯一域名 较强的带外执行证据,仍需确认来源进程和时间

六、版本核验的正确姿势

不要只依赖登录页指纹或扫描器猜版本。更可靠的方式:

  1. 查 Maven pom.xml / Gradle 依赖锁定文件;
  2. 查部署包 WEB-INF/lib 里的 shiro-core-*.jar、shiro-web-*.jar;
  3. 读 JAR 的 META-INF/MANIFEST.MF 或算哈希比对;
  4. 核对运行进程实际加载的 JAR,而不是源码声明——防止”代码已升级、运行包未替换”;
  5. 容器镜像和历史遗留包单独排查。

七、把研判固化成流程

1
2
3
4
5
6
Shiro 告警
↓ ① 指纹层:rememberMe=deleteMe?→ 提供框架或探测线索,需交叉核验
↓ ② 意图层:Cookie 解密出 Gadget/命令字符串?→ 证明攻击意图与构造能力
↓ ③ 版本层:受影响版本 + 可用密钥?→ 证明漏洞成立条件
↓ ④ 结果层:回显/进程/文件/网络/日志证据?→ 证明利用成功
↓ ⑤ 影响层:数据泄露/横向/持久化?→ 定级与处置

每一层只回答自己能回答的问题,不越层下结论——这就是 Shiro 告警研判最核心的方法论。

修复建议(给防守方)

  1. 升级到最新版本,杜绝默认密钥;
  2. 配置随机、独立、定期轮换的 RememberMe 密钥,禁止从示例代码复制、多系统共用;
  3. 评估是否真的需要 RememberMe 功能,不需要就关闭;
  4. 对 rememberMe Cookie 设置合理的长度上限与频率监控;
  5. 主机侧对 Java 进程的子进程创建、异常外联做检测。

本文整理自驻场期间对 Shiro 告警的专题研究与研判实践,所有 Payload 相关内容仅用于防御研判学习。

参考资料