背景
前段时间在做一套业务系统的安全巡检时,WAF 和应用日志里同时出现了多次针对 rememberMe 参数的异常请求。业务本身使用过 Apache Shiro 做登录态管理,虽然已经升级过版本,但历史配置和依赖没有系统梳理过,所以这次把它当成一次反序列化风险排查来完整复盘。
这篇文章不讨论利用细节,也不提供可直接用于攻击的 payload。重点放在真实环境里如何判断风险是否存在、如何定位配置问题、如何做加固和监控。类似思路也适用于其他 Java 反序列化风险排查。
问题场景
最初的异常来自两类日志:
- 网关日志中出现大量携带超长
rememberMeCookie 的请求; - 应用日志中偶发
Unable to deserialize argument byte array、Padding exception、Invalid AES key length等异常; - 同一来源 IP 会探测多个路径,包括登录页、后台入口和一些历史接口。
单看这些日志不能直接判断系统可被利用,但可以说明有人在尝试识别 Shiro 或测试 rememberMe 相关行为。对防守侧来说,关键问题有三个:
- 当前系统是否仍启用了 rememberMe;
- 是否使用默认或弱密钥;
- 依赖链中是否存在可被反序列化触发的危险类。
技术分析
Shiro 的 rememberMe 机制在旧版本和不当配置下容易成为风险点。典型流程是:服务端把用户身份信息序列化后加密,写入浏览器 Cookie;后续请求带回 Cookie,服务端解密后反序列化恢复身份信息。
这里的风险不只在 Shiro 本身,而是多个条件叠加:
- 应用启用了 rememberMe;
- 密钥泄露、默认密钥、弱密钥或长期不轮换;
- 反序列化入口可达;
- 运行时 classpath 中存在可被滥用的 gadget 链;
- 缺少出站流量、进程行为、异常日志的监控。
也就是说,看到 rememberMe 异常并不等于一定沦陷,但足以触发一次配置、依赖和日志的联合排查。
关键排查步骤
1. 确认是否启用 rememberMe
先从配置文件和代码入手,不要只依赖版本判断。很多系统升级过框架,但旧配置仍然保留。
grep -R "rememberMe" ./src ./config ./conf 2>/dev/null
grep -R "CookieRememberMeManager\|rememberMeManager\|cipherKey" ./src ./config ./conf 2>/dev/null需要重点关注以下内容:
- 是否显式配置了
CookieRememberMeManager; - 是否配置了固定
cipherKey; - 是否存在从配置中心、环境变量读取密钥的逻辑;
- 测试环境和生产环境是否共用密钥;
- 历史分支、旧部署包中是否保留默认配置。
如果业务并不需要“记住我”功能,建议直接关闭。很多内部系统没有这个功能需求,但仍沿用了示例配置。
2. 检查 Shiro 及相关依赖版本
版本检查不能只看 Shiro,还要看整个依赖树。反序列化风险经常和 commons-collections、commons-beanutils、groovy、spring-core 等依赖有关。
# Maven 项目
mvn dependency:tree | grep -E "shiro|commons-collections|commons-beanutils|groovy|spring-core|xalan|rome"
# Gradle 项目
./gradlew dependencies | grep -E "shiro|commons-collections|commons-beanutils|groovy|spring-core|xalan|rome"排查时建议记录三类信息:
- 直接依赖版本:项目自己声明的依赖;
- 传递依赖版本:由其他组件引入的依赖;
- 运行时实际版本:最终打进包里或容器加载的 jar。
我遇到过几次情况,pom 文件已经升级,但容器里的旧 jar 没清理,最终运行时仍加载旧版本。生产排查时最好直接在部署目录确认。
find /opt/app -name "*.jar" | grep -E "shiro|commons-collections|commons-beanutils|groovy"3. 分析日志中的异常特征
日志分析的目标不是还原攻击过程,而是确认是否存在可疑访问、异常触发和后续行为。可以从网关、应用、主机三个层面做关联。
# 示例:筛选带 rememberMe 的请求
zgrep -i "rememberMe" /var/log/nginx/access.log* | head -n 20
# 示例:统计来源 IP
zgrep -i "rememberMe" /var/log/nginx/access.log* | awk '{print $1}' | sort | uniq -c | sort -nr | head
# 示例:应用异常关键字
zgrep -Ei "deserialize|rememberMe|BadPadding|Padding|cipher|AES" /opt/app/logs/*.log*比较有价值的字段包括:
- 请求时间线:异常请求是否集中爆发;
- 来源地址和 User-Agent:是否存在批量扫描特征;
- 请求路径:是否命中真实业务入口;
- 响应状态码和响应大小:是否出现与正常请求明显不同的结果;
- 异常栈:是否进入 Shiro rememberMe 处理逻辑;
- 异常后是否出现新进程、异常 DNS、异常外连。
如果应用日志只有解密失败、padding 异常,通常说明请求没有成功通过解密或反序列化。但这不能作为彻底安全的依据,仍要继续检查配置和依赖。
4. 核查密钥管理方式
密钥是排查重点。常见问题包括:使用公开示例密钥、多个环境共用密钥、密钥写死在代码里、密钥长期不轮换。
grep -R "cipherKey\|kPH\|4AvV\|rememberMe" ./src ./config ./conf 2>/dev/null这里的 grep 只是用于发现可疑硬编码,实际排查还应覆盖配置中心、环境变量、启动参数和容器编排配置。
# 查看 Java 启动参数中是否传入相关配置
ps -ef | grep java | grep -i "shiro\|remember\|cipher"
# 查看环境变量中是否存在相关项
env | grep -Ei "shiro|remember|cipher|key"密钥管理建议:
- 不要使用示例密钥或网上公开密钥;
- 不同环境使用不同密钥;
- 密钥不要硬编码进仓库;
- 使用配置中心或密钥管理系统统一管理;
- 定期轮换,并评估轮换对存量 Cookie 的影响。
5. 判断是否存在异常主机行为
如果日志显示请求已经进入反序列化流程,建议同时查看主机侧行为。重点不是“是否看到某个 payload”,而是找异常进程、异常网络连接、异常文件落地。
# 查看最近启动的可疑进程
ps -eo pid,ppid,lstart,cmd --sort=start_time | tail -n 30
# 查看 Java 进程的网络连接
ss -antp | grep java
# 查看应用目录近期变更文件
find /opt/app -type f -mtime -3 -ls
# 查看临时目录近期文件
find /tmp /var/tmp -type f -mtime -3 -ls 2>/dev/null如果部署了 EDR、HIDS 或容器审计,也可以关联同一时间段的以下事件:
- Java 进程拉起 shell、curl、wget、python、perl 等子进程;
- 对外发起异常 DNS、HTTP、TCP 连接;
- 应用目录、临时目录出现未知脚本或二进制文件;
- 计划任务、启动项、SSH key 被修改。
没有发现异常行为,通常可以降低事件等级;但如果发现 Java 进程有异常子进程或外连,应按入侵事件处理,保留现场再处置。
加固建议
| 风险点 | 建议措施 |
| 不需要 rememberMe | 直接关闭相关功能,避免保留无用攻击面 |
| 固定或弱密钥 | 更换为高强度随机密钥,按环境隔离并定期轮换 |
| 依赖版本陈旧 | 升级 Shiro 和相关依赖,清理运行时旧 jar |
| 危险反序列化入口 | 尽量避免原生 Java 反序列化,使用白名单或更安全的数据格式 |
| 缺少监控 | 增加 rememberMe 异常、反序列化异常、Java 子进程、异常外连告警 |
如果必须保留 rememberMe,可以考虑进一步收敛:
- 缩短 Cookie 有效期;
- 设置
HttpOnly、Secure、SameSite属性; - 登录态和 rememberMe 分离,不赋予高权限操作能力;
- 敏感操作要求重新认证;
- 在网关层对异常长度和异常格式 Cookie 做监控。
检测规则思路
防护侧可以基于行为和日志做一些低成本检测。注意不要只依赖单一关键字,否则误报和漏报都比较明显。
# Nginx 日志中统计异常长度 Cookie 的简单思路
awk 'length($0) > 4000 && tolower($0) ~ /rememberme/ {print}' /var/log/nginx/access.log更实用的检测条件可以组合:
- 请求中包含
rememberMe=,且 Cookie 长度明显超过正常值; - 短时间内同一 IP 对多个路径发送异常 rememberMe;
- 应用日志出现 Shiro 解密、padding、反序列化相关异常;
- 异常请求后 Java 进程出现外连或子进程创建;
- 同源地址同时扫描其他常见漏洞路径。
经验上,单纯出现 padding 异常更常见于扫描和探测;如果同时伴随 Java 进程异常行为,就要按高优先级事件处理。
处置注意点
- 不要在生产环境直接做攻击性验证,容易影响业务,也可能破坏现场;
- 如果怀疑已被入侵,先保留日志、进程、网络连接、文件时间线,再重启或清理;
- 升级依赖后要确认运行时 jar 已替换,避免“构建已升级、部署仍旧版”;
- 更换 rememberMe 密钥会导致旧 Cookie 失效,需要提前评估用户体验;
- 排查范围不要只看单个应用,统一认证、后台管理、历史系统都要覆盖。
总结
Shiro rememberMe 相关风险的本质,是“可控数据进入反序列化流程”与“密钥和依赖管理不当”叠加后的结果。实际排查时,不建议只停留在版本号或单条漏洞公告上,而应该从功能启用、密钥配置、依赖树、运行时 jar、日志异常和主机行为几个维度一起看。
这类问题的长期治理思路也比较明确:不需要的功能直接关闭;必须保留的功能做好密钥管理和权限收敛;依赖定期盘点;日志和主机行为建立基线。这样即使遇到批量扫描,也能快速判断风险边界,避免在不确定状态下盲目处置。
推荐意见