最后更新:2026-04-18
Apache Shiro 是 Java 生态中广泛使用的安全框架,提供认证、授权、加密和会话管理功能。Shiro 的 RememberMe 功能使用 AES 加密序列化的用户身份信息存储在 Cookie 中,但由于历史上使用硬编码默认密钥且未对反序列化数据进行完整性校验,攻击者可构造恶意 Cookie 触发反序列化 RCE。本文档整理 Shiro 框架相关的安全问题。
| 属性 | 值 |
|---|---|
| CVE | CVE-2016-4437 |
| Bug ID | SHIRO-550 |
| 影响版本 | Apache Shiro < 1.2.5 |
| 严重程度 | 严重 |
| 利用条件 | 使用默认 RememberMe 密钥且 classpath 中存在可利用的 Gadget 链 |
漏洞原理:Shiro 的 CookieRememberMeManager 使用 AES-128-CBC 模式加密 RememberMe Cookie。在 1.2.4 及之前版本中,加密密钥为硬编码的默认值 kPH+bIxk5D2deZiIxcaaaA==。攻击者可以:
rememberMe Cookie攻击流程:
rememberMe=deleteMe 响应头)rememberMe Cookie检测方法:
# 检测 Shiro 框架(响应中含 rememberMe=deleteMe)
curl -I http://target
# 或发送无效 rememberMe Cookie
curl -b "rememberMe=1" -I http://target
# 若返回 Set-Cookie: rememberMe=deleteMe,则确认使用 Shiro
# 使用 Shiro 漏洞检测工具
python shiro_exploit.py -u http://target -k kPH+bIxk5D2deZiIxcaaaA==
修复措施:
<!-- 升级 Shiro 到安全版本 -->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-core</artifactId>
<version>1.13.0</version>
</dependency>
| 属性 | 值 |
|---|---|
| CVE | CVE-2019-12422 |
| Bug ID | SHIRO-682 |
| 影响版本 | Apache Shiro < 1.4.2 |
| 严重程度 | 高危 |
| 利用条件 | 使用 CBC 模式的 RememberMe 加密 |
漏洞原理:Shiro 使用 AES-CBC 模式加密 RememberMe Cookie,但没有使用 HMAC 等机制验证密文完整性。攻击者可以利用 Padding Oracle 攻击:通过向服务器发送修改后的 Cookie 并观察服务器的不同响应(正常解密 vs 填充错误),逐字节推断出明文或构造合法密文,从而在不知道密钥的情况下也能伪造恶意 RememberMe Cookie。
攻击条件:
修复措施:
<!-- 升级 Shiro 到 1.4.2+,使用 GCM 模式替代 CBC -->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-core</artifactId>
<version>1.4.2</version>
</dependency>
// [VULNERABLE] 使用默认密钥(或常见泄露密钥)
@Bean
public CookieRememberMeManager rememberMeManager() {
CookieRememberMeManager manager = new CookieRememberMeManager();
// 默认密钥 kPH+bIxk5D2deZiIxcaaaA== 或其他常见密钥
return manager;
}
// [SECURE] 使用强随机密钥,且定期轮换
@Bean
public CookieRememberMeManager rememberMeManager() {
CookieRememberMeManager manager = new CookieRememberMeManager();
// 使用安全的随机密钥(至少 128 位)
byte[] cipherKey = org.apache.shiro.codec.Base64.decode("YourSecureRandomBase64KeyHere==");
manager.setCipherKey(cipherKey);
return manager;
}
// [VULNERABLE] Shiro < 1.4.2 默认不限制反序列化类
// 攻击者可利用 classpath 中的 Gadget 链
// [SECURE] 升级 Shiro 并配置反序列化过滤器
@Bean
public CookieRememberMeManager rememberMeManager() {
CookieRememberMeManager manager = new CookieRememberMeManager();
// Shiro 1.4.2+ 支持配置反序列化过滤器
manager.setCipherKey(generateSecureKey());
// 限制可反序列化的类
// Shiro 2.x 默认使用反序列化过滤器
return manager;
}
<!-- [VULNERABLE] 使用存在漏洞的旧版本 -->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-core</artifactId>
<version>1.2.4</version>
</dependency>
<!-- [SECURE] 使用安全版本 -->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-core</artifactId>
<version>1.13.0</version>
</dependency>
// [VULNERABLE] RememberMe Cookie 配置不安全
@Bean
public SimpleCookie rememberMeCookie() {
SimpleCookie cookie = new SimpleCookie("rememberMe");
cookie.setMaxAge(2592000); // 30 天
cookie.setHttpOnly(false); // 允许 JS 访问
cookie.setSecure(false); // 允许 HTTP 传输
return cookie;
}
// [SECURE] 安全的 Cookie 配置
@Bean
public SimpleCookie rememberMeCookie() {
SimpleCookie cookie = new SimpleCookie("rememberMe");
cookie.setMaxAge(86400); // 缩短为 1 天
cookie.setHttpOnly(true); // 禁止 JS 访问
cookie.setSecure(true); // 仅 HTTPS 传输
cookie.setSameSite(SimpleCookie.SameSiteOptions.LAX); // 防止 CSRF
return cookie;
}
// 生成安全的 AES 密钥
import javax.crypto.KeyGenerator;
import javax.crypto.SecretKey;
import java.util.Base64;
public class ShiroKeyGenerator {
public static String generateKey() throws Exception {
KeyGenerator keyGen = KeyGenerator.getInstance("AES");
keyGen.init(256); // 使用 256 位密钥
SecretKey secretKey = keyGen.generateKey();
return Base64.getEncoder().encodeToString(secretKey.getEncoded());
}
}
# 将密钥配置在环境变量或密钥管理服务中
shiro:
cipherKey: ${SHIRO_CIPHER_KEY}
@Configuration
public class ShiroConfig {
@Bean
public SecurityManager securityManager() {
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
securityManager.setRealm(customRealm());
securityManager.setRememberMeManager(rememberMeManager());
return securityManager;
}
@Bean
public CookieRememberMeManager rememberMeManager() {
CookieRememberMeManager manager = new CookieRememberMeManager();
// 使用安全的随机密钥
byte[] cipherKey = org.apache.shiro.codec.Base64.decode(
System.getenv("SHIRO_CIPHER_KEY")
);
manager.setCipherKey(cipherKey);
return manager;
}
@Bean
public SimpleCookie rememberMeCookie() {
SimpleCookie cookie = new SimpleCookie("rememberMe");
cookie.setHttpOnly(true);
cookie.setSecure(true);
cookie.setMaxAge(86400);
return cookie;
}
}
// 如果业务不需要"记住我"功能,直接禁用
@Bean
public SecurityManager securityManager() {
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
securityManager.setRealm(customRealm());
// 不设置 RememberMeManager
return securityManager;
}
<!-- 检查并移除不必要的依赖,减少 Gadget 链可用性 -->
<!-- 常见危险 Gadget 来源 -->
<!-- commons-collections3.x / 4.x -->
<!-- commons-beanutils -->
<!-- spring-core / spring-beans -->
<!-- 确保仅保留必要的依赖 -->