作者: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
方法,解析request
和response
。接着根据请求利用SecurityManager
创建WebSubject
接口类型的实例,这里采用的是建造者模式。
返回WebDelegatingSubject
,然后调用Subject
的execute
方法,这里是一个回调,
接着在回调里调用executeChain
,然后调用getExecutionChain
,这里getFilterChainResolver
返回PathMatchingFilterChainResolver
,是在初始化ShiroFilterFactoryBean
里设置的,具体流程不进行分析,该类用于解析出此次请求需要执行的Filter
链,在PathMatchingFilterChainResolver
内部由FilterChainManager
维护着拦截器链,比如DefaultFilterChainManager
实现维护着url
模式与拦截器链的关系。
看下getChain
,首先获取FilterChainManager
,也就是DefaultFilterChainManager
,DefaultFilterChainManager
默认添加DefaultFilter
中声明的拦截器。
shiro url 处理
接着调用getPathWithinApplication
方法。
跟到WebUtils#getPathWithinApplication
方法, 这里Shiro
对url
的处理也是造成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
都以/
进行拆分存入数组,pattDirs
和pathDirs
。
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
,在里面会根据请求的url
在RequestMappingHandlerMapping
对象中去匹配自定义Controller
里的方法,而这些映射关系都是在初始化RequestMappingHandlerMapping
对象的过程中,根据相关注解获取到的。
最后调用getMatchingPatterns
,在spring
的AntPathMatcher#doMatch
里,将请求和配置里的url
进行匹配,如果匹配成功,返回true
,匹配之后,从HandlerMapping
中取出该路径所映射的方法,然后通过反射去执行方法。
版本修复
在shiro 1.6.0
版本中,针对/*
这种ant
风格的配置出现的问题,shiro
在org.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 发布分析