1. 项目概述:从一次真实的应急响应说起
去年,我们团队负责维护的一套核心业务系统突然收到了安全部门的紧急告警。告警信息指向一个我们使用了多年的权限认证框架——Apache Shiro。经过排查,发现攻击者利用了一个经典的Shiro反序列化漏洞,尝试在服务器上执行任意命令。虽然因为其他防护措施,攻击并未成功,但这次事件让我们惊出一身冷汗。事后复盘,我们发现开发团队虽然知道Shiro,但对这个潜伏的“老漏洞”及其变种缺乏系统性的认知和防护。这促使我花了大量时间,深入梳理了Apache Shiro反序列化漏洞的来龙去脉,并形成了一套从原理到修复、从临时缓解到彻底根治的完整解决方案。今天,我就把这些实战经验分享出来,希望能帮你绕过我们踩过的坑。
简单来说,Apache Shiro是一个强大且易用的Java安全框架,用于身份验证、授权、加密和会话管理。其反序列化漏洞的核心,在于Shiro为了提供“记住我”(RememberMe)功能而使用的默认加密方式存在缺陷。攻击者可以构造恶意的序列化数据,在服务器反序列化时触发远程代码执行(RCE)。这个漏洞影响范围极广,从2016年的CVE-2016-4437开始,后续又衍生出多个利用链和绕过方式,至今仍是红蓝对抗中的高频考点。无论你是开发、运维还是安全工程师,只要你的系统涉及Shiro,理解并解决这个问题都是必修课。
2. 漏洞原理深度拆解:为什么Shiro会成为靶子?
要真正解决问题,必须首先理解问题是如何产生的。Shiro的反序列化漏洞并非设计之初就存在的后门,而是其功能特性在特定使用方式下暴露出的安全风险。
2.1 核心祸根:RememberMe功能的加密与解密过程
Shiro的“记住我”功能,本质是在用户浏览器端存储一个加密的Cookie。当用户下次访问时,Shiro会读取这个Cookie,解密并反序列化其中的数据,从而自动完成登录。这个过程可以简化为:
- 序列化与加密(服务端 -> 客户端):用户成功登录并勾选“记住我”后,Shiro会将用户的身份信息(如PrincipalCollection)进行Java序列化,然后使用一个硬编码的默认密钥(
kPH+bIxk5D2deZiIxcaaaA==)进行AES加密,最后Base64编码,设置为Cookie(名为rememberMe)发送给浏览器。 - 解密与反序列化(客户端 -> 服务端):用户再次访问时,浏览器会携带这个
rememberMeCookie。Shiro接收到后,会进行Base64解码,使用相同的密钥进行AES解密,最后对解密后的字节流进行Java反序列化,还原用户身份信息。
漏洞就爆发在最后一步:反序列化。Java的反序列化机制本身是危险的,它会根据字节流中的类描述,自动调用类的readObject方法。如果攻击者能够控制被反序列化的数据,并让服务器反序列化一个精心构造的、包含恶意代码的类对象,就能实现RCE。
2.2 攻击链的形成:密钥与利用链的双重问题
攻击者要利用此漏洞,需要突破两个关键点:
- 获取或爆破加密密钥:由于Shiro 1.2.4及之前版本使用了众所周知的默认密钥,且很多开发者在部署时并未修改,这相当于把家门钥匙放在了门口的垫子下。即使后续版本在生成随机密钥,但如果密钥泄露(如通过代码泄露、配置文件泄露),攻击者就能伪造合法的加密Cookie。
- 构造有效的反序列化利用链(Gadget Chain):仅有密钥还不够,解密后的数据必须是一个能成功触发RCE的Java对象。这依赖于目标服务器的Classpath中是否存在可被利用的第三方库,如
CommonsCollections、CB、Hibernate等。攻击者会将这些库中一系列类的特性像搭积木一样组合起来,形成一条从反序列化入口到最终执行命令的完整调用链。
关键认知:这个漏洞的利用是“条件触发”式的。服务器上必须存在相应的利用链依赖库,漏洞才能被成功利用。这解释了为什么有些系统“中招”了,有些却没有。但绝不能抱有侥幸心理,因为现代Java应用引入这些常见库的概率非常高。
2.3 漏洞的变种与绕过:一场持续的攻防战
最初的漏洞(CVE-2016-4437)修复后,攻防并未停止。安全研究人员和攻击者发现了多种绕过方式:
- Padding Oracle Attack(CVE-2020-1957):利用AES加密的CBC模式缺陷,在不知道密钥的情况下,通过服务端返回的差异(如报错信息、响应时间)来逐字节爆破出明文或构造出合法的Padding,从而绕过密钥验证。
- 利用其他编码方式:除了Base64,攻击者尝试使用Hex等编码方式来封装Payload,以绕过一些简单的WAF规则检测。
- 新的利用链(Gadget)挖掘:随着新的第三方库被广泛使用,不断有新的利用链被挖掘出来,例如基于
CommonsBeanutils、ROME等的链,扩大了攻击面。
这些变种使得单一的修复措施往往不够,需要一套组合拳。
3. 解决方案全景图:从紧急止血到根除病根
面对Shiro反序列化漏洞,我们的应对策略应该是分层、递进的。下图概括了从应急到根治的完整路径:
发现漏洞告警 | v [ 阶段一:紧急处置(临时缓解) ] |—— 3.1 临时禁用RememberMe功能 |—— 3.2 部署WAF/IPS规则拦截 | v [ 阶段二:针对性加固(中期修复) ] |—— 3.3 升级Shiro至安全版本 |—— 3.4 修改并强化加密密钥 |—— 3.5 引入反序列化过滤器 | v [ 阶段三:架构免疫(长期治理) ] |—— 3.6 弃用Java原生反序列化 |—— 3.7 建立软件成分清单与漏洞监控接下来,我们详细拆解每一个步骤的具体操作和背后的考量。
3.1 紧急处置:发现漏洞后的第一时间响应
如果监控系统告警或怀疑已被攻击,首要任务是立即止损,为后续修复争取时间。
操作1:全局禁用RememberMe功能这是最直接、最有效的一键止血方案。通过修改Shiro的配置,彻底关闭该功能入口。
- 在Spring Boot的
application.yml中配置:shiro: # 禁用记住我功能 rememberMe: enabled: false - 在传统Shiro
ini或Java Config中:@Bean public SecurityManager securityManager(Realm realm) { DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager(); securityManager.setRealm(realm); // 不设置RememberMe管理器,或显式设置为null // securityManager.setRememberMeManager(null); return securityManager; } - 实操心得:在线上紧急处理时,我推荐直接修改配置并重启应用。虽然有些文章提到可以通过Filter拦截Cookie,但在高并发或复杂过滤链场景下,配置方式更彻底、更可靠。禁用后,所有用户的“记住我”Cookie将失效,需要重新登录,务必通过公告等方式告知用户。
操作2:部署虚拟补丁或WAF规则在应用层修复之前,可以在网络边界或应用前端(如Nginx、Apache)部署拦截规则,识别并阻断带有恶意特征的rememberMeCookie请求。
- 示例Nginx规则片段(需根据实际Payload特征调整):
location / { # 检查Cookie中rememberMe的值,如果匹配特定模式则返回403 if ($http_cookie ~* "rememberMe=([^;]+)") { set $shiro_cookie $1; # 这是一个非常简单的示例,实际应使用更复杂的正则匹配Payload特征 if ($shiro_cookie ~* "rO0ABXQ.*") { # 匹配Java序列化流的Base64开头 return 403; } } proxy_pass http://your_app_server; } - 注意事项:WAF规则容易被绕过(如编码变形、分块传输),且可能产生误报。它只能作为临时辅助手段,绝不能替代应用自身的修复。规则需要安全团队持续维护和更新。
3.2 针对性加固:修复漏洞本体的核心步骤
紧急措施稳定局面后,需要立即着手进行实质性修复。
3.2.1 升级Shiro至安全版本这是官方推荐的首选方案。新版本通常修复了已知的漏洞并可能引入更安全的默认行为。
- 目标版本:至少升级到1.7.0及以上版本。1.7.0之后,Shiro在
RememberMeManager中增强了对反序列化的防护。 - 升级步骤:
- 检查当前项目的
pom.xml或build.gradle中Shiro的版本号。 - 修改版本号为最新稳定版(如1.13.0)。注意兼容性,建议先在测试环境验证。
<dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-web</artifactId> <version>1.13.0</version> <!-- 升级至此版本或更高 --> </dependency> - 执行依赖更新命令(
mvn clean install或gradle build),解决可能出现的API变更导致的编译错误。
- 检查当前项目的
- 重要提示:仅仅升级Shiro版本并不能完全免疫反序列化攻击!如果密钥仍然弱或泄露,且Classpath中存在利用链,攻击者依然可能利用旧版Payload尝试攻击。升级必须与其他措施结合。
3.2.2 修改并强化加密密钥这是切断已知攻击路径的关键一步。务必不要使用默认密钥。
- 生成强密钥:使用Shiro提供的工具或自行生成一个足够长且随机的Base64编码密钥。
# 使用Shiro的CommandLineMain工具生成(需shiro-tools依赖) java -cp shiro-tools-hasher-1.13.0.jar org.apache.shiro.tools.Hasher -a AES -i 128 # 或者使用任何安全的随机数生成器,生成16、24或32字节的密钥,然后Base64编码 - 在配置中指定密钥:
# application.yml shiro: rememberMe: cipherKey: base64:你的新强密钥字符串 # 注意前缀 base64:// Java Config @Bean public CookieRememberMeManager rememberMeManager() { CookieRememberMeManager manager = new CookieRememberMeManager(); byte[] cipherKey = Base64.decode("你的新强密钥字符串"); manager.setCipherKey(cipherKey); // 建议同时设置Serializer,见下一节 return manager; } - 密钥管理最佳实践:
- 环境隔离:开发、测试、生产环境使用不同的密钥。
- 定期轮换:制定密钥轮换策略,尽管对RememberMe功能来说,轮换会导致所有用户退出,但可作为高风险时期的安全增强措施。
- 安全存储:将密钥存储在环境变量或专业的密钥管理服务(KMS)中,而非硬编码在配置文件里。
3.2.3 引入反序列化过滤器(白名单机制)这是目前最有效的防御手段之一。其原理是在反序列化过程中,拦截并检查即将被反序列化的类,只允许预定义的安全类被加载。
- 使用Shiro内置的
DefaultSerializer(推荐):从Shiro 1.7.0开始,CookieRememberMeManager支持设置Serializer。我们可以配置一个DefaultSerializer,并为其指定一个AllowList反序列化器。import org.apache.shiro.io.DefaultSerializer; import org.apache.shiro.subject.SimplePrincipalCollection; @Bean public CookieRememberMeManager rememberMeManager() { CookieRememberMeManager manager = new CookieRememberMeManager(); manager.setCipherKey(cipherKey); // 1. 创建允许类的集合 Set<Class<?>> allowedClasses = new HashSet<>(); allowedClasses.add(SimplePrincipalCollection.class); // Shiro身份集合,必须 allowedClasses.add(String.class); // 可能存储的用户名 allowedClasses.add(Long.class); // 可能存储的时间戳 // 添加你的业务中RememberMe功能需要序列化的所有安全类 // 2. 创建并配置允许列表序列化器 DefaultSerializer serializer = new DefaultSerializer(); // 关键:设置允许的类列表 serializer.getAllowList().addAll(allowedClasses); // 或者,更严格地,只使用允许列表,拒绝其他所有类 // serializer.setWhiteList(true); // 新版本API可能有所不同,需查证 manager.setSerializer(serializer); return manager; } - 使用第三方安全库:对于更复杂的场景或历史版本,可以集成专门的反序列化过滤库,如
SerialKiller。你需要将其封装成一个Shiro能识别的Serializer。public class SerialKillerSerializer implements Serializer<Object> { private final SerialKiller serialKiller; public SerialKillerSerializer() { // 配置SerialKiller,定义黑名单/白名单 Config config = new Config.Builder() .addAllowedClass("org.apache.shiro.subject.SimplePrincipalCollection") .addAllowedClass("java.lang.String") .setBlacklistPattern(".*\\.*") // 示例:更激进的黑名单 .build(); this.serialKiller = new SerialKiller(config); } @Override public Object deserialize(byte[] serialized) throws SerializationException { try (ByteArrayInputStream bis = new ByteArrayInputStream(serialized); ObjectInputStream ois = serialKiller.newObjectInputStream(bis)) { return ois.readObject(); } catch (Exception e) { throw new SerializationException("反序列化失败", e); } } // ... serialize 方法 } - 白名单配置的挑战:最大的难点在于确定完整的白名单列表。你需要仔细审计RememberMe功能到底序列化了哪些业务对象。建议从
SimplePrincipalCollection开始,在测试环境开启详细日志,观察正常登录时的序列化行为,逐步构建白名单。宁可开始名单过小导致部分功能需要重新登录,也绝不要盲目扩大名单。
3.3 架构免疫:面向未来的根本性解决方案
以上的加固措施主要围绕Shiro自身的功能进行。要从根本上降低此类风险,需要考虑架构层面的改进。
3.3.1 弃用或替换Java原生反序列化Java原生反序列化是万恶之源。在新的系统或模块中,应尽量避免使用ObjectInputStream/ObjectOutputStream。
- 替代方案:
- JSON(Jackson/Gson):对于简单的数据传输,JSON是更安全、更通用的选择。Shiro的Session信息可以考虑用JSON序列化后存储。
- Protocol Buffers / Thrift:对于性能要求高、跨语言交互的场景,这些二进制协议是更优选择,它们有严格的模式(Schema),天生免疫任意类加载。
- 改造RememberMe:可以自定义一个
RememberMeManager,内部使用JSON来存储用户标识(如用户ID),而非完整的PrincipalCollection对象。当Cookie被送回时,根据用户ID从数据库重建用户信息。这彻底移除了反序列化环节。public class JsonRememberMeManager extends CookieRememberMeManager { private ObjectMapper objectMapper = new ObjectMapper(); @Override protected byte[] serialize(PrincipalCollection principals) { String userId = extractUserId(principals); // 提取核心ID try { return objectMapper.writeValueAsBytes(new RememberMeToken(userId, System.currentTimeMillis())); } catch (JsonProcessingException e) { throw new SerializationException(e); } } @Override protected PrincipalCollection deserialize(byte[] serialized) { try { RememberMeToken token = objectMapper.readValue(serialized, RememberMeToken.class); // 根据token中的userId,从数据库或缓存查询并构建PrincipalCollection return rebuildPrincipals(token.getUserId()); } catch (IOException e) { throw new SerializationException(e); } } // ... 内部类 RememberMeToken }
3.3.2 建立软件成分清单与持续漏洞监控Shiro漏洞的利用依赖外部库(利用链)。管理好这些依赖至关重要。
- 使用SCA工具:集成像OWASP Dependency-Check、Snyk、GitHub Dependabot等软件成分分析工具到CI/CD流水线中。每次构建都自动检查项目依赖库的已知漏洞(包括Shiro本身及其潜在的危险依赖如Commons-Collections)。
- 维护许可与安全策略:明确禁止引入已知的高危库(如
commons-collections:3.1)。如果业务必须使用,需经过安全团队评审,并制定额外的防护措施。 - 订阅安全通告:关注Apache Shiro官方安全页面、国家漏洞库(CNNVD/NVD)以及安全社区,一旦有新的漏洞披露,能第一时间评估影响并响应。
4. 实战操作记录:一步步修复一个Spring Boot项目
理论说再多,不如亲手做一遍。假设我们有一个使用Spring Boot 2.7和Shiro 1.8.0的遗留项目,现在需要对其进行加固。
4.1 环境诊断与现状分析
首先,我们通过检查来了解现状。
- 检查依赖:打开
pom.xml,确认Shiro版本。<dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-spring-boot-web-starter</artifactId> <version>1.8.0</version> <!-- 存在漏洞的版本 --> </dependency> - 检查配置:查找
application.yml或Java Config中关于rememberMe的配置。发现使用了默认配置,且没有显式设置密钥。 - 检查依赖树中的危险库:运行
mvn dependency:tree,搜索commons-collections、commons-beanutils等常见利用链组件。
发现存在mvn dependency:tree | grep -E "commons-collections|commons-beanutils"commons-collections:3.2.2。
4.2 分步实施修复
我们采取组合策略:升级 + 修改密钥 + 设置白名单。
步骤一:升级Shiro版本修改pom.xml,将版本升级至1.13.0。由于是次版本号升级,需要关注API变更。查阅官方发布说明,1.9.0到1.13.0之间没有破坏性变更,但测试必不可少。
<dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-spring-boot-web-starter</artifactId> <version>1.13.0</version> </dependency>运行mvn clean compile,确保编译通过。
步骤二:生成并配置强密钥使用在线工具或代码生成一个随机的32字节(256位)AES密钥,并Base64编码。
import javax.crypto.KeyGenerator; import java.util.Base64; //... KeyGenerator keyGen = KeyGenerator.getInstance("AES"); keyGen.init(256); // 使用256位密钥 byte[] key = keyGen.generateKey().getEncoded(); String base64Key = Base64.getEncoder().encodeToString(key); System.out.println("新密钥: " + base64Key);将生成的密钥配置到application.yml中:
shiro: rememberMe: cipherKey: base64:q4vJ7FgT2XpL9MkR8YwC1zAnB5DcE6HjN # 替换为你的密钥 # 注意:旧Cookie将全部失效,用户需重新登录步骤三:配置反序列化白名单(Java Config方式)如果项目使用的是Java配置类,修改或创建Shiro的配置类。
@Configuration public class ShiroConfig { @Value("${shiro.rememberMe.cipherKey}") private String cipherKey; @Bean public CookieRememberMeManager rememberMeManager() { CookieRememberMeManager manager = new CookieRememberMeManager(); // 1. 设置强密钥 byte[] keyBytes = Base64.decode(cipherKey.replace("base64:", "")); manager.setCipherKey(keyBytes); // 2. 创建并配置带白名单的序列化器 DefaultSerializer serializer = new DefaultSerializer(); Set<Class<?>> allowedClasses = new HashSet<>(); allowedClasses.add(SimplePrincipalCollection.class); allowedClasses.add(String.class); allowedClasses.add(Long.class); allowedClasses.add(ArrayList.class); // SimplePrincipalCollection内部可能使用 allowedClasses.add(HashMap.class); // 同上 // 将允许列表设置给序列化器 // 注意:Shiro 1.13.0中,DefaultSerializer的AllowList可能通过构造器或setter设置 // 此处为示例,具体API请查阅对应版本文档 // serializer.setAllowedClasses(allowedClasses); // 另一种方式:使用BeanSerializer(如果可用)或自定义Serializer manager.setSerializer(new SafeSerializer(allowedClasses)); return manager; } // 自定义安全序列化器 static class SafeSerializer implements Serializer<Object> { private final Set<String> allowedClassNames; private final DefaultSerializer defaultSerializer = new DefaultSerializer(); SafeSerializer(Set<Class<?>> allowedClasses) { this.allowedClassNames = allowedClasses.stream() .map(Class::getName) .collect(Collectors.toSet()); } @Override public Object deserialize(byte[] serialized) throws SerializationException { // 简易实现:先反序列化,再检查类型(生产环境需更严谨,如重写ObjectInputStream) Object obj = defaultSerializer.deserialize(serialized); if (obj != null && !allowedClassNames.contains(obj.getClass().getName())) { throw new SerializationException("反序列化检测到非法类: " + obj.getClass().getName()); } return obj; } @Override public byte[] serialize(Object object) throws SerializationException { return defaultSerializer.serialize(object); } } // 将RememberMe管理器注入SecurityManager @Bean public SecurityManager securityManager(Realm realm, CookieRememberMeManager rememberMeManager) { DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager(); securityManager.setRealm(realm); securityManager.setRememberMeManager(rememberMeManager); // 注入 return securityManager; } }步骤四:测试与验证
- 功能测试:启动应用,测试正常登录、记住我登录、退出功能是否正常。
- 漏洞验证:使用安全扫描工具(如Burp Suite的Shiro插件、专门的Shiro检测脚本)对修复后的服务进行检测,确认漏洞是否已被修复。
- 回归测试:确保系统的其他功能不受影响。
4.3 配置清单与检查表
为了确保修复的完整性,你可以使用下表进行核对:
| 检查项 | 操作说明 | 预期结果/配置示例 | 是否完成 |
|---|---|---|---|
| Shiro版本 | 升级至1.7.0+ | shiro-spring-boot-starter:1.13.0 | ☐ |
| RememberMe密钥 | 已从默认密钥修改 | cipherKey: base64:你的强随机密钥 | ☐ |
| 反序列化过滤器 | 已配置白名单机制 | 仅允许SimplePrincipalCollection,String,Long等必要类 | ☐ |
| 危险依赖 | 检查并评估commons-collections等 | 考虑升级至无害版本或排除 | ☐ |
| 会话存储 | 确认Session管理器未使用不安全序列化 | 如使用EnterpriseCacheSessionDAO,确保缓存安全 | ☐ |
| WAF/IPS规则 | 已部署或更新拦截规则 | 能识别常见Shiro攻击Payload | ☐ |
| 监控告警 | 已配置对异常登录/反序列化错误的监控 | 出现相关异常能及时告警 | ☐ |
5. 常见问题与排查技巧实录
在实际操作中,你肯定会遇到各种问题。下面是我在多次修复和加固过程中积累的一些典型问题及其解决方法。
5.1 修复过程中的典型问题
问题1:升级Shiro后,应用启动报错,提示NoSuchMethodError或ClassNotFoundException。
- 原因:这通常是依赖冲突或API不兼容导致的。Shiro的新版本可能依赖了不同版本的
slf4j、spring或其他库。 - 排查:
- 运行
mvn dependency:tree -Dincludes=org.apache.shiro查看Shiro相关依赖树,确认传递依赖的版本。 - 检查是否有其他依赖强制指定了旧版Shiro组件。
- 运行
- 解决:
- 在
pom.xml中显式声明可能冲突的公共依赖的版本(通过<dependencyManagement>)。 - 使用
mvn dependency:analyze分析依赖。 - 清理本地Maven仓库(
~/.m2/repository/org/apache/shiro),重新拉取依赖。
- 在
问题2:配置了白名单后,正常的“记住我”功能也失效了,用户无法自动登录。
- 原因:白名单过于严格,遗漏了RememberMe功能实际序列化的某些类。Shiro在
SimplePrincipalCollection中可能存储了自定义的Principal对象或使用了特定的Map/Collection实现。 - 排查:
- 开启调试日志:在
application.yml中设置logging.level.org.apache.shiro=DEBUG。 - 正常登录一个用户并勾选“记住我”,观察日志中序列化和反序列化过程中的类名信息。
- 在反序列化失败时,日志通常会打印出试图加载的类名。
- 开启调试日志:在
- 解决:
- 将日志中看到的类逐步添加到白名单中。
- 一个更稳妥但繁琐的方法是:在自定义的
Serializer的deserialize方法中,捕获异常并打印出试图反序列化的类名,然后将其评估后加入白名单。 - 如果业务复杂,可以考虑转向使用JSON序列化方案,一劳永逸。
问题3:修改密钥后,所有已登录用户被迫退出,用户体验不好。
- 原因:这是预期行为。新的密钥无法解密旧的Cookie。
- 解决:
- 沟通与公告:如果必须立即修改密钥,应提前发布维护公告,告知用户。
- 平滑过渡(如果架构允许):实现一个短暂的“双密钥”支持期。自定义一个
RememberMeManager,在解密时先用新密钥尝试,失败后再用旧密钥尝试。一旦用旧密钥解密成功,就用新密钥重新加密并设置Cookie。过渡期结束后移除旧密钥逻辑。此方案实现复杂,需谨慎评估。
5.2 漏洞排查与应急响应技巧
技巧1:如何快速判断系统是否存在Shiro漏洞?
- 手动检测:发送一个请求,在Cookie中设置
rememberMe=xxx(任意值)。观察响应。- 如果返回的
Set-Cookie头中也有rememberMe=deleteMe,基本可以确定目标使用了Shiro,并且RememberMe功能已开启(存在漏洞利用条件)。 - 使用Burp Suite的
Shiro Scanner插件或开源工具(如ShiroAttack2)进行更精确的检测和密钥爆破。
- 如果返回的
- 日志分析:在Shiro的
RememberMeManager相关类中增加审计日志,记录解密失败和反序列化失败的信息。频繁的解密失败日志可能意味着正在遭受密钥爆破攻击。
技巧2:服务器疑似被入侵,如何排查?
- 立即隔离:将疑似服务器从网络中断开,防止横向移动。
- 保护现场:对内存、磁盘进行镜像备份,以便后续取证。
- 日志溯源:
- 重点检查应用日志中Shiro相关的错误(如解密异常、反序列化异常)。
- 检查Web访问日志(如Nginx、Tomcat access log),寻找带有超长或特殊字符
rememberMeCookie的请求记录,记录下源IP和时间。 - 检查系统命令执行历史(
~/.bash_history)和进程列表(ps auxf),查找可疑进程。
- 文件排查:在Web目录(如
/tmp,/dev/shm)下查找可疑的.jsp、.war或脚本文件,攻击者常会上传Webshell。 - 时间线关联:将攻击请求的时间点与服务器上可疑文件创建时间、异常进程启动时间进行关联分析。
技巧3:修复后如何进行有效性验证?
- 工具复测:使用之前成功的攻击Payload和工具,再次对修复后的系统进行测试,应全部失败。
- 代码审计:重点审计
RememberMeManager、Serializer、CipherService等相关配置类的代码,确保加固措施已正确生效。 - 渗透测试:邀请安全团队或使用专业的渗透测试服务进行一次针对性的测试。
修复Apache Shiro反序列化漏洞是一个系统工程,涉及从紧急响应到代码修复,再到架构优化的多个层面。没有一劳永逸的银弹,关键在于理解漏洞原理,采取纵深防御策略,并将安全实践融入到开发和运维的日常流程中。每次安全事件都是改进的契机,希望这份详细的解决方案能成为你构建更安全系统的一块坚实基石。