Shiro从入门到权限绕过漏洞
字数 1859 2025-08-24 16:48:15
Apache Shiro 从入门到权限绕过漏洞分析
一、Shiro 简介
Apache Shiro 是一个强大且易用的 Java 安全框架,用于身份验证、授权、加密和会话管理。它可以用于保护任何应用程序,从命令行应用程序、移动应用程序到大型的 Web 和企业应用程序。
Shiro 的核心架构
Shiro 架构由以下几个核心组件组成:
-
Subject:代表当前用户或当前程序,是一个接口,定义了认证和授权的方法
- 认证:判断用户是否为合法用户的过程
- 授权:认证成功后分配权限,决定用户可以访问哪些资源
-
SecurityManager:安全管理器,负责认证和授权
- 通过 Authenticator 认证器进行认证
- 通过 Authorizer 授权器进行授权
- 通过 SessionManager 会话管理器进行会话管理
-
Authenticator:认证器,负责身份认证
- 从 Realm 获取用户数据进行身份验证
-
Authorizer:授权器,判断用户身份拥有的权限
-
Realm:数据源,相当于数据库
- 从数据库(如 MySQL)获取用户信息
- 包含认证和授权相关操作
-
SessionManager:会话管理器
- 不依赖 Web 容器的 session
- 可用于非 Web 应用
- 支持分布式应用的会话集中管理
-
SessionDAO:会话存储
- 可通过 JDBC 将会话存储到数据库
二、Shiro 认证流程
认证基本概念
- 身份信息 (Principal):用户名、邮箱等标识信息
- 凭据信息 (Credential):密码、证书等
认证流程
- 用户携带身份信息和凭据信息(用户名和密码)
- Shiro 将用户名和密码封装成 Token
- 通过 SecurityManager 安全管理器
- SecurityManager 调用 Authenticator 认证器
- Authenticator 调用 Realm 获取数据并进行比对
- 比对成功则认证成功,否则认证失败
认证代码示例
// 1. 创建安全管理器对象
DefaultSecurityManager securityManager = new DefaultSecurityManager();
// 2. 给安全管理器设置realm
securityManager.setRealm(new IniRealm("classpath:shiro.ini"));
// 3. SecurityUtils 给全局安全工具类设置安全管理器
SecurityUtils.setSecurityManager(securityManager);
// 4. 获取主体 Subject
Subject subject = SecurityUtils.getSubject();
// 5. 创建令牌
UsernamePasswordToken token = new UsernamePasswordToken("relaysec", "123456");
try {
subject.login(token); // 用户认证
System.out.println("登录成功");
} catch (UnknownAccountException e) {
System.out.println("认证失败: 用户名不存在~");
} catch (IncorrectCredentialsException e) {
System.out.println("认证失败: 密码错误~");
}
认证源码分析
-
用户名认证:
- 调用
Subject.login()方法 - 实际调用
DefaultSecurityManager.login() - 调用
authenticator.authenticate() - 最终在
SimpleAccountRealm.doGetAuthenticationInfo()中实现用户名处理
- 调用
-
密码认证:
- 调用
assertCredentialsMatch()方法 - 使用密码匹配器进行密码比对
- 默认使用
equals方法比对 - 如果密码加密或加盐,会有其他处理方式
- 调用
三、Shiro 授权
授权流程
- 用户认证成功后登录系统
- 判断用户是否有权限访问请求的资源
- 有权限则允许访问,否则拒绝访问
四、Spring Boot 整合 Shiro
依赖配置
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-web</artifactId>
<version>1.7.0</version>
</dependency>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>1.7.0</version>
</dependency>
Shiro 配置类
@Configuration
public class ShiroConfig {
// 1. 创建ShiroFilter
@Bean
public ShiroFilterFactoryBean getShiroFilterFactoryBean(DefaultWebSecurityManager defaultWebSecurityManager) {
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
shiroFilterFactoryBean.setSecurityManager(defaultWebSecurityManager);
// 配置系统资源权限
Map<String, String> map = new HashMap<>();
map.put("/admin/**", "anon"); // 公共资源
map.put("/admin/users", "authc"); // 需要认证和授权
map.put("/demo/**", "anon");
map.put("/index.jsp", "authc");
shiroFilterFactoryBean.setLoginUrl("/login.jsp");
shiroFilterFactoryBean.setFilterChainDefinitionMap(map);
return shiroFilterFactoryBean;
}
// 2. 创建安全管理器
@Bean
public DefaultWebSecurityManager getDefaultWebSecurityManager(Realm realm) {
DefaultWebSecurityManager defaultWebSecurityManager = new DefaultWebSecurityManager();
defaultWebSecurityManager.setRealm(realm);
return defaultWebSecurityManager;
}
// 3. 创建自定义Realm
@Bean
public Realm getRealm() {
CustomerRealm customerRealm = new CustomerRealm();
return customerRealm;
}
}
自定义 Realm 示例
public class CustomerRealm extends AuthorizingRealm {
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
return null;
}
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
String principal = (String) token.getPrincipal();
String password_db = "123";
String username_db = "zhangsan";
if (username_db.equals(principal)) {
return new SimpleAuthenticationInfo(principal, "123", this.getName());
}
return null;
}
}
五、Shiro 过滤流程分析
- 请求到达 Tomcat,以责任链形式调用 Filter
OncePerRequestFilter调用doFilterInternalPathMatchingFilterChainResolver.getChain处理过滤- 获取请求路径
- 进行路径标准化处理
- 与配置的权限规则进行匹配
- 匹配成功后创建
ProxiedFilterChain代理对象处理请求
六、Shiro 权限绕过漏洞分析
CVE-2020-1957
环境要求:
- Spring Boot: 2.2.6.RELEASE
- Shiro: 1.5.0
漏洞原理:
- Shiro 和 Spring 对 URL 分号处理不一致
- Shiro 不会过滤分号,而 Spring 会过滤分号
- 导致权限检查绕过
漏洞复现:
- 配置权限规则:
map.put("/admin/*", "authc"); // 需要认证 map.put("/demo/**", "anon"); // 公共资源 - 绕过方式:
/demo/..;/admin/index
漏洞分析:
-
Shiro 处理:
- 获取请求路径
//demo/..;/admin/users - 截取分号前部分:
//demo/.. - 标准化处理后:
/demo/.. - 匹配
/demo/**规则,允许访问
- 获取请求路径
-
Spring 处理:
- 过滤掉分号,实际路径变为
/demo/../admin/users - 标准化处理后:
/admin/users - 最终访问的是受保护的
/admin/users资源
- 过滤掉分号,实际路径变为
修复建议:
- 升级 Shiro 到最新版本
- 避免使用过于宽松的 URL 匹配规则
- 对关键资源实施多重保护机制