CVE-2020-1957 Apache Shiro Servlet未授权访问浅析
字数 1635 2025-08-20 18:17:53

Apache Shiro Servlet未授权访问漏洞(CVE-2020-1957)深度分析与教学文档

漏洞概述

CVE-2020-1957是Apache Shiro与Spring Boot集成时存在的一个权限绕过漏洞。攻击者可以构造特殊的URL,利用Shiro和Spring Boot对URL解析的差异,绕过Shiro的权限控制,实现未授权访问受保护的Servlet。

环境要求

  • Java环境:Java(TM) SE Runtime Environment (build 1.8.0_112-b16)
  • Apache Shiro版本:1.5.1
  • Spring Boot版本:1.5.22.RELEASE

漏洞复现环境搭建

项目依赖配置(pom.xml)

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" 
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>1.5.22.RELEASE</version>
        <relativePath/>
    </parent>
    <modelVersion>4.0.0</modelVersion>
    <artifactId>cve-2020-1957</artifactId>
    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <configuration>
                    <source>7</source>
                    <target>7</target>
                </configuration>
            </plugin>
        </plugins>
    </build>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.apache.shiro</groupId>
            <artifactId>shiro-web</artifactId>
            <version>1.5.1</version>
        </dependency>
        <dependency>
            <groupId>org.apache.shiro</groupId>
            <artifactId>shiro-spring</artifactId>
            <version>1.5.1</version>
        </dependency>
    </dependencies>
</project>

核心代码实现

1. Realm实现类

public class Realm extends AuthorizingRealm {
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
        return null;
    }

    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) 
        throws AuthenticationException {
        String username = (String) token.getPrincipal();
        if (!"rai4over".equals(username)) {
            throw new UnknownAccountException("账户不存在!");
        }
        return new SimpleAuthenticationInfo(username, "123456", getName());
    }
}

2. Shiro配置类

@Configuration
public class ShiroConfig {
    @Bean
    MyRealm myRealm() {
        return new MyRealm();
    }

    @Bean
    SecurityManager securityManager() {
        DefaultWebSecurityManager manager = new DefaultWebSecurityManager();
        manager.setRealm(myRealm());
        return manager;
    }

    @Bean
    ShiroFilterFactoryBean shiroFilterFactoryBean() {
        ShiroFilterFactoryBean bean = new ShiroFilterFactoryBean();
        bean.setSecurityManager(securityManager());
        bean.setLoginUrl("/login");
        bean.setSuccessUrl("/index");
        bean.setUnauthorizedUrl("/unauthorizedurl");
        
        Map<String, String> map = new LinkedHashMap<>();
        map.put("/login", "anon");
        map.put("/xxxxx/**", "anon");
        map.put("/aaaaa/**", "anon");
        map.put("/admin", "authc");
        map.put("/admin.*", "authc");
        map.put("/admin/**", "authc");
        map.put("/**", "authc");
        
        bean.setFilterChainDefinitionMap(map);
        return bean;
    }
}

3. 测试Controller

@RestController
public class TestController {
    @RequestMapping(value = "/login")
    public String login(String username, String password) {
        Subject subject = SecurityUtils.getSubject();
        try {
            subject.login(new UsernamePasswordToken(username, password));
            return "登录成功!";
        } catch (AuthenticationException e) {
            e.printStackTrace();
            return "登录失败!";
        }
    }

    @RequestMapping(value = "/admin", method = RequestMethod.GET)
    public String admin() {
        return "admin secret bypass and unauthorized access";
    }

    @RequestMapping(value = "/xxxxx", method = RequestMethod.GET)
    public String xxxxx() {
        return "xxxxx";
    }
}

漏洞复现步骤

  1. 正常访问/xxxxx路径 - 无需认证即可访问
  2. 直接访问/admin路径 - 会被重定向到登录页面
  3. 构造恶意URL访问/xxxxx/..;/admin - 成功绕过认证访问admin内容

漏洞原理深度分析

Shiro处理流程

  1. 请求拦截:恶意请求/xxxxx/..;/admin首先经过Shiro的PathMatchingFilterChainResolver#getChain方法处理

  2. 路径解析

    • getPathWithinApplication方法负责解析请求路径
    • WebUtils#getRequestUri获取原始URI /xxxxx/..;/admin
    • decodeAndCleanUriString方法将;后面的内容截断,变为/xxxxx/..
    • normalize方法对路径进行规范化处理,结果仍为/xxxxx/..
  3. 权限校验

    • 规范化后的路径/xxxxx/..与过滤器规则/xxxxx/**匹配成功
    • 由于/xxxxx/**配置为anon(允许匿名访问),请求通过Shiro校验

Spring Boot处理流程

  1. 请求路由:通过Shiro校验的请求进入Spring Boot的UrlPathHelper#getPathWithinServletMapping

  2. Servlet路径解析

    • getServletPath方法从请求上下文中获取Servlet路径
    • Spring Boot的解析器将/xxxxx/..;/admin解析为/admin
    • 最终路由到/admin对应的Controller方法

关键差异点

  • Shiro的路径解析

    • ;作为分隔符,截断后面的内容
    • ..的处理不够彻底,保留了路径遍历的痕迹
    • 最终匹配的是/xxxxx/../xxxxx/**
  • Spring Boot的路径解析

    • 能够正确处理;和路径遍历符..
    • 最终解析出真实的请求路径/admin

漏洞修复方案

Apache Shiro在后续版本中修复了此漏洞,主要修改了requestURI的获取方式:

  1. 修复commit: https://github.com/apache/shiro/commit/3708d7907016bf2fa12691dff6ff0def1249b8ce
  2. 关键修改:改进了路径解析逻辑,确保更准确地获取请求URI

防御建议

  1. 升级Apache Shiro到最新版本
  2. 在Shiro配置中,对敏感路径使用更严格的匹配规则
  3. 避免使用过于宽松的通配符(如/**)
  4. 实施多层防御机制,不单纯依赖Shiro的URL过滤

总结

CVE-2020-1957漏洞揭示了在安全框架集成时可能存在的解析差异问题。通过深入分析Shiro和Spring Boot对URL处理的不同方式,我们可以更好地理解这类权限绕过漏洞的本质。开发人员在集成多个安全组件时,应当特别注意各组件对请求处理的细微差异,避免形成安全盲点。

Apache Shiro Servlet未授权访问漏洞(CVE-2020-1957)深度分析与教学文档 漏洞概述 CVE-2020-1957是Apache Shiro与Spring Boot集成时存在的一个权限绕过漏洞。攻击者可以构造特殊的URL,利用Shiro和Spring Boot对URL解析的差异,绕过Shiro的权限控制,实现未授权访问受保护的Servlet。 环境要求 Java环境:Java(TM) SE Runtime Environment (build 1.8.0_ 112-b16) Apache Shiro版本:1.5.1 Spring Boot版本:1.5.22.RELEASE 漏洞复现环境搭建 项目依赖配置(pom.xml) 核心代码实现 1. Realm实现类 2. Shiro配置类 3. 测试Controller 漏洞复现步骤 正常访问 /xxxxx 路径 - 无需认证即可访问 直接访问 /admin 路径 - 会被重定向到登录页面 构造恶意URL访问 /xxxxx/..;/admin - 成功绕过认证访问admin内容 漏洞原理深度分析 Shiro处理流程 请求拦截 :恶意请求 /xxxxx/..;/admin 首先经过Shiro的 PathMatchingFilterChainResolver#getChain 方法处理 路径解析 : getPathWithinApplication 方法负责解析请求路径 WebUtils#getRequestUri 获取原始URI /xxxxx/..;/admin decodeAndCleanUriString 方法将 ; 后面的内容截断,变为 /xxxxx/.. normalize 方法对路径进行规范化处理,结果仍为 /xxxxx/.. 权限校验 : 规范化后的路径 /xxxxx/.. 与过滤器规则 /xxxxx/** 匹配成功 由于 /xxxxx/** 配置为 anon (允许匿名访问),请求通过Shiro校验 Spring Boot处理流程 请求路由 :通过Shiro校验的请求进入Spring Boot的 UrlPathHelper#getPathWithinServletMapping Servlet路径解析 : getServletPath 方法从请求上下文中获取Servlet路径 Spring Boot的解析器将 /xxxxx/..;/admin 解析为 /admin 最终路由到 /admin 对应的Controller方法 关键差异点 Shiro的路径解析 : 将 ; 作为分隔符,截断后面的内容 对 .. 的处理不够彻底,保留了路径遍历的痕迹 最终匹配的是 /xxxxx/.. 与 /xxxxx/** Spring Boot的路径解析 : 能够正确处理 ; 和路径遍历符 .. 最终解析出真实的请求路径 /admin 漏洞修复方案 Apache Shiro在后续版本中修复了此漏洞,主要修改了 requestURI 的获取方式: 修复commit: https://github.com/apache/shiro/commit/3708d7907016bf2fa12691dff6ff0def1249b8ce 关键修改:改进了路径解析逻辑,确保更准确地获取请求URI 防御建议 升级Apache Shiro到最新版本 在Shiro配置中,对敏感路径使用更严格的匹配规则 避免使用过于宽松的通配符(如 /** ) 实施多层防御机制,不单纯依赖Shiro的URL过滤 总结 CVE-2020-1957漏洞揭示了在安全框架集成时可能存在的解析差异问题。通过深入分析Shiro和Spring Boot对URL处理的不同方式,我们可以更好地理解这类权限绕过漏洞的本质。开发人员在集成多个安全组件时,应当特别注意各组件对请求处理的细微差异,避免形成安全盲点。