跳转到帖子

背景

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 shiro

Gradle 项目可以使用:

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 反序列化问题的价值不在于“又复现了一个老洞”,而在于它提醒我们:安全风险往往来自历史配置、默认值、废弃功能和依赖组合。对防守方来说,最重要的是把确认、修复和监控流程做扎实。

实际处理时,建议按“确认功能是否启用、检查密钥和版本、隔离环境验证、关闭或加固、补充监控、清理历史资产”的顺序推进。这样既能避免不必要的破坏性测试,也能把修复结果落到可验证的状态。

代码审计与漏洞定位示意图
代码审计、调用链与关键函数定位示意

0篇意见

推荐意见

没有意见。

游客
抱歉,你的帖子内容包括我们不允许的字词。请编辑你的帖子,删除下面高亮的屏蔽字。
添加意见…