CVE-2020-13933:Apache Shiro 权限绕过漏洞分析

 

作者:Hu3sky@360CERT

0x01 漏洞简述

2020年08月18日, 360CERT监测发现 Apache Shiro 发布了 Apahce Shiro 权限绕过 的风险通告,该漏洞编号为 CVE-2020-13933,漏洞等级:高危,漏洞评分:8.0

Apahce Shiro 由于处理身份验证请求时出错 存在 权限绕过漏洞,远程攻击者可以发送特制的HTTP请求,绕过身份验证过程并获得对应用程序的未授权访问。

对此,360CERT建议广大用户及时将 Apache Shiro 升级到最新版本。与此同时,请做好资产自查以及预防工作,以免遭受黑客攻击。

 

0x02 风险等级

360CERT对该漏洞的评定结果如下

评定方式 等级
威胁等级 高危
影响面 广泛
360CERT评分 8.0分

 

0x03 影响版本

  • Apache Shiro < 1.6.0

 

0x04 漏洞详情

相关概念

shiro的架构图为: 

其中最主要的是 Subject,SecurityManager,Realms

Subject:主体,代表了当前“用户”,这个用户不一定是一个具体的人,与当前应用交互的任何东西都是Subject,如网络爬虫,机器人等;即一个抽象概念;所有Subject都绑定到SecurityManager,与Subject的所有交互都会委托给SecurityManager;可以把Subject认为是一个门面;SecurityManager才是实际的执行者;

SecurityManager:安全管理器;即所有与安全有关的操作都会与SecurityManager交互;且它管理着所有Subject;可以看出它是Shiro的核心,它负责与后边介绍的其他组件进行交互,如果学习过SpringMVC,你可以把它看成DispatcherServlet前端控制器;

Realm:域,Shiro从从Realm获取安全数据(如用户、角色、权限),就是说SecurityManager要验证用户身份,那么它需要从Realm获取相应的用户进行比较以确定用户身份是否合法;也需要从Realm得到用户相应的角色/权限进行验证用户是否能进行操作;可以把Realm看成DataSource,即安全数据源。

漏洞原理

利用idea搭建springboot+shiro进行测试,shiro版本为1.5.3

        <dependency>
            <groupId>org.apache.shiro</groupId>
            <artifactId>shiro-core</artifactId>
            <version>1.5.3</version>
        </dependency>

        <dependency>
            <groupId>org.apache.shiro</groupId>
            <artifactId>shiro-web</artifactId>
            <version>1.5.3</version>
        </dependency>

        <dependency>
          <groupId>org.apache.shiro</groupId>
          <artifactId>shiro-spring</artifactId>
          <version>1.5.3</version>
</dependency>

构建测试代码。

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

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

    //filter工厂.设置对应的过滤条件和跳转条件
    @Bean
    public ShiroFilterFactoryBean shiroFilter() {
        ShiroFilterFactoryBean bean = new ShiroFilterFactoryBean();
        // 设置 SecurityManager
        bean.setSecurityManager(securityManager());
        bean.setLoginUrl("/login");
        Map<String, String> filterMap = new LinkedHashMap<>();
        // anon:匿名用户可访问
        filterMap.put("/login","anon");
        // authc:认证用户可访问
        filterMap.put("/read/*", "authc");
        bean.setFilterChainDefinitionMap(filterMap);
        return bean;
    }
}

具体权限对应的定义在DefaultFilter类里。

访问/read/xx,被302重定向到了/login

/read/%3bxxx,能够绕过认证:

ant风格的路径仅出现一个*时才能成功,而**无法绕过,具体原因后面会说。

先来看看shiro的拦截器基础类。

部分拦截器的作用:

PathMatchingFilter: 提供了基于Ant风格的请求路径匹配功能及拦截器参数解析的功能。

OncePerRequestFilter: 过滤器基类,该基类保证每个请求在任何servlet容器上仅执行一次,另外提供enabled属性,表示是否开启该拦截器实例,默认enabled=true表示开启,如果不想让某个拦截器工作,可以设置为false即可。

AbstractShiroFilter: 是Shiro的入口,根据URL配置的filter,选择并执行相应的filter chain。

shiro层

shiro 过滤器链调用

接着从源码来分析一下这个漏洞,首先是过滤器的调用,经过ApplicationFilterChain,请求被分派到OncePerRequestFilter过滤器进行拦截,首先调用getAlreadyFilteredAttributeName给我们的自定义过滤器打上标记,并通过判断当前请求中是否有该标记来判断该拦截器是否已经调用过,接着判断是否标记该拦截器不进行工作,

如果都没有,那么调用doFilterInternal方法,这里调用的是其子类AbstractShiroFilter#doFilterInternal方法,解析requestresponse。接着根据请求利用SecurityManager创建WebSubject接口类型的实例,这里采用的是建造者模式。

返回WebDelegatingSubject,然后调用Subjectexecute方法,这里是一个回调,

接着在回调里调用executeChain,然后调用getExecutionChain,这里getFilterChainResolver返回PathMatchingFilterChainResolver,是在初始化ShiroFilterFactoryBean里设置的,具体流程不进行分析,该类用于解析出此次请求需要执行的Filter链,在PathMatchingFilterChainResolver内部由FilterChainManager维护着拦截器链,比如DefaultFilterChainManager实现维护着url模式与拦截器链的关系。

看下getChain,首先获取FilterChainManager,也就是DefaultFilterChainManagerDefaultFilterChainManager默认添加DefaultFilter中声明的拦截器。

shiro url 处理

接着调用getPathWithinApplication方法。

跟到WebUtils#getPathWithinApplication方法, 这里Shirourl的处理也是造成CVE-2020-11989的一个点。左边是修复之前的1.5.2版本,右边是当前版本1.5.3,采用getRequestUri,而getRequestUri里就进行了url的解码。

修复之后,采用了标准的url解析,不再对%2f解码。

getServletPath方法将%3b进行了解码,返回结果如下。

接着调用removeSemicolon,该方法查找;,并将;及其之后的部分删除,最后再调用normalize,用于操作./所带来的影响。本次测试返回/read/

接着如果请求的不是/,就去除末尾的/,也就是/read

接着获取filterChains所对应的filter,将处理后的url进行匹配。

shiro url 匹配

org.apache.shiro.util.AntPathMatcher#doMatch。 这里把我们定义的ant风格的path和请求的path都以/进行拆分存入数组,pattDirspathDirs

pattern最后一位是*,于是返回false

如果是**,会返回true,返回true的话,就会根据config里设置url所对应 filter过滤条件,最后返回ProxiedFilterChain

如果是*,返回false,那么会return null

resolved也就是null,最后就返回ApplicationFilterChain,在ApplicationFilterChain里没有任何权限校验。

spring 层

原则是Shiro会对Servlet容器的FilterChain进行代理,也就是正常情况下应该返回的ProxiedFilterChain,即先走Shiro自己的Filter体系,然后才会委托给Servlet容器的FilterChain进行Servlet容器级别的Filter链执行。但是我们利用*%3b的配合绕过了,返回的依然是默认的ApplicationFilterChain,后续调用chain.doFilter

spring url 处理

spring利用DispatcherServlet来分派请求,他的一个主要作用就是通过HandlerMapping,将请求映射到处理器。在处理过程中会调用getHandler方法。

跟进getHandlerInternal方法。

这里会调用 UrlPathHelper#getLookupPathForRequest 方法获取请求的相对路径。

继续调用getPathWithinApplication方法获取地址。

然后调用getRequestUri,这里调用decodeAndCleanUriString方法很明显的对url进行解码,也就是/read/;xxxx,于是最终spring处理返回的url就是/read/;xxxx

调用完getLookupPathForRequest之后,就是获取请求路径的映射了。

spring 获取路径映射

跟入lookupHandlerMethod,在里面会根据请求的urlRequestMappingHandlerMapping对象中去匹配自定义Controller里的方法,而这些映射关系都是在初始化RequestMappingHandlerMapping对象的过程中,根据相关注解获取到的。

最后调用getMatchingPatterns,在springAntPathMatcher#doMatch里,将请求和配置里的url进行匹配,如果匹配成功,返回true,匹配之后,从HandlerMapping中取出该路径所映射的方法,然后通过反射去执行方法。

版本修复

shiro 1.6.0版本中,针对/*这种ant风格的配置出现的问题,shiroorg.apache.shiro.spring.web.ShiroFilterFactoryBean.java中默认增加了/**的路径配置,以防止出现匹配不成功的情况。

而默认的/**配置对应了一个新增的类org.apache.shiro.web.filter.InvalidRequestFilter进行过滤,匹配到非法字符就会直接报错。

总结

该漏洞产生的原因主要是shiro层在处理url上和spring上存在差异,主要是在处理;上的问题,通过构造含有;符号的url即可绕过shiro在权限上的处理,而spring不负责权限管控,所以最终会导致权限绕过。

 

0x05 时间线

2020-08-17 Apache Shiro发布通告

2020-08-18 360-CERT 发布预警

2020-09-22 360-CERT 发布分析

 

0x06 参考链接

  1. CVE-2020-13933: Apache Shiro 权限绕过漏洞通告
  2. 第八章 拦截器机制——《跟我学Shiro》
  3. SpringMVC源码解析(四)——请求处理
  4. 【原创】005 | 搭上SpringBoot请求处理源码分析专车
  5. Apache Shiro 身份验证绕过漏洞 (CVE-2020-11989)
(完)