Shiro反序列化源码分析学习
字数 1499 2025-08-18 11:35:59
Shiro反序列化漏洞深入分析与利用教学
1. 漏洞概述
Shiro是一个Java安全框架,提供身份验证、授权、密码学和会话管理功能。Shiro550(CVE-2016-4437)和Shiro721(CVE-2019-12422)是两个著名的反序列化漏洞:
- Shiro550:由于使用硬编码的AES加密密钥导致的反序列化漏洞
- Shiro721:Padding Oracle攻击导致的身份验证绕过漏洞
2. 环境搭建
- 使用Vulhub搭建测试环境:
git clone https://github.com/vulhub/vulhub.git
cd vulhub/shiro/CVE-2016-4437
docker-compose up -d
- 获取运行中的JAR包:
docker ps # 获取容器ID
docker cp <container_id>:/path/to/shirodemo-1.0-SNAPSHOT.jar .
- 使用IDEA分析:
- 创建项目
- 新建libs目录并放入JAR包
- 右键libs目录选择"Add as Library"
3. 漏洞分析流程
3.1 路由分析
通过MANIFEST.MF文件找到入口类,分析UserController:
@Controller
public class UserController {
@PostMapping({"/doLogin"})
public String doLoginPage(@RequestParam("username") String username,
@RequestParam("password") String password,
@RequestParam(name = "rememberme", defaultValue = "") String rememberMe) {
Subject subject = SecurityUtils.getSubject();
try {
subject.login(new UsernamePasswordToken(username, password, rememberMe.equals("remember-me")));
return "forward:/";
} catch (AuthenticationException var6) {
return "forward:/login";
}
}
// 其他路由方法...
}
关键点:
rememberme参数控制是否启用"记住我"功能UsernamePasswordToken构造方法处理认证信息
3.2 认证流程分析
- Subject创建:
Subject subject = SecurityUtils.getSubject();
- 登录流程:
subject.login(new UsernamePasswordToken(...));
- 深入
login方法:
- 进入
DelegatingSubject类的login方法 - 调用
securityManager.login(this, token)
- 认证过程:
DefaultSecurityManager.authenticate()AbstractAuthenticator.doAuthenticate()AuthenticatingRealm.getAuthenticationInfo()
3.3 反序列化触发点
关键路径:
DefaultSecurityManager.createSubject()DefaultSecurityManager.resolvePrincipals()DefaultSecurityManager.getRememberedIdentity()AbstractRememberMeManager.getRememberedPrincipals()
关键代码:
public PrincipalCollection getRememberedPrincipals(SubjectContext subjectContext) {
byte[] bytes = this.getRememberedSerializedIdentity(subjectContext);
if (bytes != null && bytes.length > 0) {
return this.convertBytesToPrincipals(bytes, subjectContext);
}
}
3.4 漏洞核心
-
数据处理流程:
- 从Cookie获取
rememberMe值 - Base64解码
- AES解密(使用硬编码密钥)
- 反序列化解密后的数据
- 从Cookie获取
-
硬编码密钥:
public AbstractRememberMeManager() {
this.serializer = new DefaultSerializer();
this.cipherService = new AesCipherService();
this.setCipherKey(DEFAULT_CIPHER_KEY_BYTES); // 硬编码密钥
}
- 反序列化点:
protected PrincipalCollection convertBytesToPrincipals(byte[] bytes, SubjectContext context) {
bytes = this.decrypt(bytes); // AES解密
return this.deserialize(bytes); // 反序列化
}
4. 漏洞利用
4.1 利用工具
使用ShiroAttack2工具:
java -jar shiro_attack-4.7.0-SNAPSHOT-all.jar
4.2 利用步骤
- 检测Shiro框架
- 使用内置密钥检测
- 选择利用链(如CommonsBeanutils1)
- 构造恶意序列化对象
- 执行命令
4.3 Debug验证
- 启动应用并附加调试器:
java -agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=65500 -jar shirodemo-1.0-SNAPSHOT.jar
- 关键断点设置:
AbstractRememberMeManager.getRememberedPrincipals()AbstractRememberMeManager.convertBytesToPrincipals()AbstractRememberMeManager.decrypt()AbstractRememberMeManager.deserialize()
5. 漏洞修复
- 升级到最新版本(≥1.2.5)
- 自定义加密密钥:
public class CustomRememberMeManager extends CookieRememberMeManager {
public CustomRememberMeManager() {
setCipherKey(Base64.decode("your_random_base64_encoded_key"));
}
}
- 配置Shiro:
@Bean
public SecurityManager securityManager() {
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
securityManager.setRememberMeManager(new CustomRememberMeManager());
return securityManager;
}
6. 总结
Shiro550漏洞的核心在于:
- 使用硬编码AES密钥(
kPH+bIxk5D2deZiIxcaaaA==) - 对用户控制的rememberMe cookie值进行反序列化
- 缺乏对反序列化对象的白名单校验
通过分析这个漏洞,我们可以学习到:
- Java反序列化漏洞的原理
- Shiro框架的认证流程
- 加密机制的安全实现
- 漏洞挖掘和利用的思路