背景
Shiro 反序列化问题已经不是新漏洞,但在实际资产排查和事件复盘里仍然经常出现。原因通常不是团队不知道这个风险,而是历史系统多、依赖版本混乱、默认密钥残留、网关改造后暴露面变化等问题叠加,导致风险长期存在。
这篇文章基于一次内部安全告警的复盘整理,重点放在如何安全确认风险、理解漏洞成因、做代码与配置层面的加固。文中不提供武器化利用链,也不讨论未授权控制主机等内容,只记录对防守和代码审计有价值的细节。
问题场景
某业务系统接入统一认证前,曾经单独使用 Apache Shiro 管理登录态。后续虽然改造到统一登录,但旧的 Shiro 依赖和过滤器配置没有完全移除,线上仍然返回 rememberMe 相关 Cookie。
监控侧看到异常请求集中命中以下特征:
- 请求中存在异常长度的 rememberMe Cookie;
- 部分请求触发后端反序列化异常日志;
- 访问路径并不集中在登录接口,而是覆盖多个普通业务路径;
- 异常来源 IP 分散,疑似自动化扫描。
从防守角度看,这类现象至少说明两个问题:第一,应用仍然启用了 Shiro rememberMe 处理逻辑;第二,应用可能暴露了可被探测的反序列化入口。
漏洞原理简析
Shiro 的 rememberMe 机制会把序列化后的身份信息加密后写入 Cookie。服务端收到请求时,如果发现 rememberMe Cookie,会尝试进行解密、反序列化和身份恢复。
问题的关键在于:如果应用使用了默认或弱配置的加密密钥,攻击者可以构造可被服务端解密的数据,使服务端进入反序列化流程。若运行环境中存在危险的 gadget 链,就可能产生更严重影响。
需要注意的是,风险是否成立通常取决于多个条件:
- Shiro 版本和配置是否存在问题;
- rememberMe 功能是否启用;
- 密钥是否为默认值、弱值或泄露值;
- 运行时 classpath 中是否存在可触发的反序列化链;
- JDK 版本、依赖版本和安全策略是否限制了利用条件。
因此在企业内部复现时,不建议只看版本就下结论,也不建议直接使用不可信脚本对生产系统做探测。更稳妥的方法是结合配置、日志、依赖和受控环境验证。
安全复现思路
在授权范围内,建议优先采用“非破坏性确认”的方式。核心目标不是证明能执行任意命令,而是确认服务端是否仍然处理 rememberMe、是否使用了不安全密钥、是否进入了反序列化流程。
第一步:确认应用是否启用 rememberMe
可以从响应头、Cookie 和配置文件入手。常见配置位置包括 shiro.ini、Spring XML、Java Config、Spring Boot 配置类等。
[main]
securityManager.rememberMeManager.cipherKey = ...
[urls]
/login = anon
/logout = logout
/** = user如果系统已经接入统一认证,但仍然保留 Shiro Filter,需要重点检查 rememberMeManager 是否还在生效。很多系统以为“登录不用 Shiro 了”,但过滤器链仍然会处理请求。
第二步:检查依赖版本和运行环境
对 Maven 项目可以直接导出依赖树:
mvn dependency:tree | grep -i shiroGradle 项目可以使用:
gradle dependencies | grep -i shiro排查时不要只看 shiro-core,还要关注 shiro-web、shiro-spring 等模块。部分项目会通过公共 starter 间接引入 Shiro,业务仓库里未必能直接看到版本号。
同时建议记录 JDK 版本、常见序列化相关依赖,例如 commons-collections、commons-beanutils、groovy、rome 等。这里不是为了构造攻击链,而是为了判断运行时是否存在高风险组合,方便后续制定修复优先级。
第三步:通过日志确认异常类型
如果应用收到异常 rememberMe 数据,Shiro 通常会在解密、反序列化或 Cookie 清理阶段留下日志。可以重点搜索以下关键词:
rememberMe
CookieRememberMeManager
Unable to deserialize
SerializationException
PaddingException
BadPaddingException
ClassNotFoundException如果日志中出现反序列化异常,基本可以证明服务端确实在处理相关 Cookie。若同时存在大量异常来源和不同 payload 长度,需要考虑被扫描或尝试利用。
第四步:在隔离环境验证配置风险
建议复制应用配置到隔离环境,使用相同版本依赖和 JDK,开启详细日志,验证 rememberMe 处理路径是否存在。验证重点包括:
- 是否使用默认 cipherKey;
- 是否配置了固定密钥且长期未轮换;
- 是否不需要 rememberMe 但仍启用;
- 异常 Cookie 是否触发反序列化流程;
- 升级版本后行为是否变化。
这类验证不需要执行危险载荷。只要能证明不可信 Cookie 会被服务端解密并进入反序列化逻辑,就足够支撑漏洞定级和修复。
代码审计关注点
审计 Shiro 风险时,我通常会看以下几类代码和配置。
1. RememberMeManager 配置
如果业务不需要记住登录状态,建议直接关闭 rememberMe,而不是仅仅更换密钥。
@Bean
public DefaultWebSecurityManager securityManager(Realm realm) {
DefaultWebSecurityManager manager = new DefaultWebSecurityManager();
manager.setRealm(realm);
manager.setRememberMeManager(null);
return manager;
}如果必须使用 rememberMe,需要使用高强度随机密钥,并且纳入密钥管理流程,避免写死在代码仓库中。
2. 过滤器链是否过宽
有些系统把所有路径都交给 Shiro 处理,即使静态资源、健康检查、网关回调也会经过相关过滤器。过滤器链越宽,暴露面越大。
[urls]
/assets/** = anon
/health = anon
/api/public/** = anon
/** = authc这里要结合业务实际调整。不要为了省事把所有路径统一配置为 user 或 authc,也不要让废弃系统继续挂在主域名下。
3. 历史默认密钥残留
一些老项目可能直接复制网上示例配置,或者使用框架默认值。审计时可以搜索:
cipherKey
rememberMeManager
CookieRememberMeManager
base64CipherKey
setCipherKey如果发现密钥出现在 Git 历史、配置中心公共空间、镜像环境变量或部署脚本中,都应视为已泄露处理。
4. 高风险反序列化依赖
反序列化风险不只属于 Shiro。只要项目中存在 Java 原生反序列化入口,就应梳理可被触达的依赖。建议在依赖治理中标记高风险库,逐步升级或移除。
mvn dependency:tree | egrep "commons-collections|commons-beanutils|groovy|rome|xalan"如果短期无法移除,可以通过 JDK 序列化过滤器降低风险。
加固建议
针对 Shiro rememberMe 风险,比较稳妥的修复顺序如下。
1. 优先关闭不必要的 rememberMe
如果业务没有明确需求,直接禁用是最有效的方案。很多内部系统并不需要长期登录态,关闭 rememberMe 对用户影响有限,却能减少一类反序列化入口。
2. 升级 Shiro 版本
升级不是万能的,但旧版本往往同时存在多个安全问题。升级前建议在测试环境验证认证流程、会话管理、权限注解和异常处理,避免只改版本号导致线上登录异常。
3. 更换并保护密钥
如果必须保留 rememberMe,应使用随机生成的密钥,并放入受控的密钥管理系统。更换后要考虑旧 Cookie 失效带来的用户体验问题,一般表现为用户需要重新登录。
# 示例:生成 256-bit 随机值,仅用于说明生成方式
openssl rand -base64 32不要把生成后的密钥提交到代码仓库,也不要在多个环境复用同一密钥。
4. 增加序列化过滤
对于较新的 JDK,可以使用 JEP 290 的 ObjectInputFilter 限制反序列化类型、深度和数组大小。这个措施适合做纵深防御,但不能替代漏洞修复。
-Djdk.serialFilter=maxdepth=10;maxrefs=1000;maxbytes=1048576;!* 实际配置不能照抄,需要根据业务确实需要反序列化的类型设置白名单,否则可能影响正常功能。
5. 在网关和 WAF 增加监测
可以对 rememberMe Cookie 的异常长度、异常字符分布、频繁失败来源做监测。注意这里更适合作为发现和告警,不应作为唯一防护。
- 记录出现 rememberMe Cookie 的路径和来源;
- 统计解密失败、反序列化失败次数;
- 对异常请求进行限速或阻断;
- 将异常日志接入 SIEM 或集中日志平台。
事件复盘中的几个坑
这类问题在复盘时容易出现几个误区。
误区一:只看 Shiro 版本
版本重要,但配置同样重要。某些情况下,新版本如果保留了不合理配置,也可能存在较大风险;旧版本如果根本没有启用相关功能,风险面又会不同。结论应该基于版本、配置、日志和运行时依赖综合判断。
误区二:生产环境直接跑利用工具
这会带来额外风险,包括误触发命令执行、写入垃圾文件、打挂服务、污染日志等。内部验证应尽量使用隔离环境和非破坏性方法。
误区三:修完代码但忘记清理历史暴露面
不少系统修复主应用后,测试环境、预发布环境、旧域名、容器镜像仍然保留问题配置。资产侧需要同步排查,不然过一段时间又会出现同类告警。
排查清单
| 检查项 | 建议动作 |
| 是否启用 rememberMe | 无业务需求则关闭 |
| Shiro 版本 | 升级到受维护版本并完成回归测试 |
| cipherKey | 使用随机密钥,禁止默认值和硬编码 |
| 依赖风险 | 梳理反序列化相关高风险库 |
| 日志监控 | 监控 rememberMe 异常和反序列化异常 |
| 历史环境 | 排查旧域名、测试环境、镜像和配置中心 |
总结
Shiro rememberMe 反序列化问题的价值不在于“又复现了一个老洞”,而在于它提醒我们:安全风险往往来自历史配置、默认值、废弃功能和依赖组合。对防守方来说,最重要的是把确认、修复和监控流程做扎实。
实际处理时,建议按“确认功能是否启用、检查密钥和版本、隔离环境验证、关闭或加固、补充监控、清理历史资产”的顺序推进。这样既能避免不必要的破坏性测试,也能把修复结果落到可验证的状态。
推荐意见