Apache Shiro 身份验证绕过漏洞复现 (cve-2020-1957)

0x00 漏洞描述

Apache Shiro 1.5.2之前版本中存在安全漏洞。攻击者可借助特制的请求利用该漏洞绕过身份验证。
Shiro框架通过拦截器功能来对用户访问权限进行控制,如anon, authc等拦截器。anon为匿名拦截器,不需要登录即可访问;authc为登录拦截器,需要登录才可以访问。Shiro的URL路径表达式为Ant格式,路径通配符*表示匹配零个或多个字符串,/*可以匹配/hello,但是匹配不到/hello/,因为*通配符无法匹配路径。假设/hello接口设置了authc拦截器,访问/hello会进行权限判断,但如果访问的是/hello/,那么将无法正确匹配URL,直接放行,进入到spring拦截器。spring中的/hello和/hello/形式的URL访问的资源是一样的,从而实现了权限绕过。

0x01 漏洞影响

Apache Shiro < 1.5.2

0x02  Shiro拦截器

Shiro框架通过拦截器功能来实现对用户访问权限的控制和拦截。Shiro中常见的拦截器有anon,authc等拦截器。
1.anon为匿名拦截器,不需要登录就能访问,一般用于静态资源,或者移动端接口
2.authc为登录拦截器,需要登录认证才能访问的资源。
用户可以在Shiro.ini编写匹配URL配置,将会拦截匹配的URL,并执行响应的拦截器。从而实现对URL的访问控制,URL路径表达式通常为ANT格式。如下配置,访问 /index.html主页的时候,Shiro将不会对其进行登录判断,anon拦截器不需要登录就能进行访问。而对于/user/xiaoming 等 /user/xiaogang等接口,authc拦截器将会对其进行登录判断,有登录认证才能访问资源。
[urls] /index.html = anon /user/** = authc
Shiro的URL路径表达式为Ant 格式,路径通配符支持 ? * ** 。
?:匹配一个字符 *:匹配零个或多个字符串 **:匹配路径中的零个或多个路径
其中*表示匹配零个或多个字符串,/*可以匹配/hello,但匹配不到/hello/因为*通配符无法匹配路径。假设/hello接口设置了authc拦截器,访问/hello将会被进行权限判断,如果请求的URI为/hello/呢,/*URL路径表达式将无法正确匹配,放行。然后进入到spring(Servlet)拦截器,spring中/hello 形式和/hello/形式的URL访问的资源是一样的。

0x03  环境搭建

下载demo代码 :https://github.com/lenve/javaboy-code-samples/tree/master/shiro/shiro-basic
导入idea
Shiro版本1.4.2
    <dependency>
        <groupId>org.apache.shiro</groupId>
        <artifactId>shiro-web</artifactId>
        <version>1.4.2</version>
    </dependency>
    <dependency>
        <groupId>org.apache.shiro</groupId>
        <artifactId>shiro-spring</artifactId>
        <version>1.4.2</version>
    </dependency>
修改ShiroConfig配置文件,添加authc拦截器的拦截正则
    @Bean
    ShiroFilterFactoryBean shiroFilterFactoryBean() {
        ShiroFilterFactoryBean bean = new ShiroFilterFactoryBean();
        ...
        ...
        //map.put("/*", "authc");
        map.put("/hello/*", "authc"); 
        bean.setFilterChainDefinitionMap(map);
        return bean;
    }
修改路由控制器方法
@GetMapping("/hello/{currentPage}")
    public String hello(@PathVariable Integer currentPage) {
        return "hello";
}
通过idea进行编译,可获得war包
这里通过docker快速搭建漏洞环境:
docker pull vulfocus/shiro-cve_2020_1957
docker images
docker run -d -p 8080:8080 -v /var/run/docker.sock:/var/run/docker.sock -e VUL_IP=192.168.1.14  29136b1d3c61

-v /var/run/docker.sock:/var/run/docker.sock 为 docker 交互连接。
-e DOCKER_URL 为 Docker 连接方式,默认通过 unix://var/run/docker.sock 进行连接,也可以通过 tcp://xxx.xxx.xxx.xxx:2375 进行连接(必须开放 2375 端口)。
-v /vulfocus-api/db.sqlite3:db.sqlite3 映射数据库为本地文件。
-e VUL_IP=xxx.xxx.xxx.xxx 为 Docker 服务器 IP ,不能为 127.0.0.1。  
http://192.168.1.14:8080/login



0x04 漏洞复现

1、Shiro1.4.2版本绕过权限
访问/hello/1接口,可以看到被authc拦截器拦截了,将会跳转到登录接口进行登录。


访问/hello/1/,成功绕过authc拦截器,获取到了资源。




2、Shiro1.4.2版本绕过漏洞分析
漏洞初始成因可以定位到 PathMatchingFilterChainResolver的getChain函数下,该函数作用根据URL路径匹配中配置的url路径表达式来匹配输入的URL,判断是否匹配拦截器,匹配成功将会返回响应的拦截器执行链,让ShiroFither执行权限操作的。
其对于URL路径表达式和输入URL的匹配主要通过pathMathches函数进行匹配。
pathMatches函数其最终会调用shiro.util.AntPathMatcher类中doMatch的对于ant格式的pathPattern和requestURI进行匹配。
//pathMatches:135, PathMatchingFilterChainResolver (org.apache.shiro.web.filter.mgt) protected boolean pathMatches(String pattern, String path) { PatternMatcher pathMatcher = this.getPathMatcher(); return pathMatcher.matches(pattern, path); }
doMatch:109, AntPathMatcher (org.apache.shiro.util),当Shiro 的Ant格式的pathPattern 中的的*通配符是不支持匹配路径的,所以/hello/* 不能成功匹配/hello/1/ ,也就不会触发authc拦截器进行权限拦截。从而成功绕过了Shiro拦截器,而后再进入到spring拦截器中,/hello/1/与/hello/1能获取到相同的资源。

3、Shiro≤1.5.1版本绕过
在1.5.1版本中,/hello/会直接跳转到登录

绕过payload,/fdsf;/../hello/1,成功绕过。
或者其他payload,xxxx/..;/hello/1,成功绕过(shiro的1.5.1及其之前的版本)

其中以上处理过程:
  1. 客户端请求URL: /xxxx/..;/hello/1
  2. shrio 内部处理得到校验URL为 /xxxx/..,校验通过
  3. springboot 处理 /xxxx/..;/hello/1 , 最终请求 /hello, 成功访问了后台请求.
4.Shiro≤1.5.1版本漏洞分析
问题同样可以定位到getChain函数中对于requestURI的获取中,如下图所示,this.getPathWithinApplication(request)获取的requestURI为/fdsf ,而不是我们输入的/fdsf;/../hello/1,从而导致后面的URI路径模式匹配返回False,从而再次绕过了shiro拦截器。
getPathWithinApplication函数中会调用WebUtils (org.apache.shiro.web.util)中的getRequestUri函数获取RequestUri。
public static String getRequestUri(HttpServletRequest request) {
        String uri = (String)request.getAttribute("javax.servlet.include.request_uri");
        if (uri == null) {
            uri = request.getRequestURI();
        }

        return normalize(decodeAndCleanUriString(request, uri));
    }

RequestUri函数中最终调用decodeAndCleanUriString函数对URI进行清洗。

private static String decodeAndCleanUriString(HttpServletRequest request, String uri) {
      uri = decodeRequestString(request, uri);
      int semicolonIndex = uri.indexOf(59);//获取;号的位置
      return semicolonIndex != -1 ? uri.substring(0, semicolonIndex) : uri;
  }
如果URI中存在;号的话,则会删除其后面的所有字符。/fdsf;/../hello/1/最终也就变成了/fdsf


5.漏洞总结
在web容器中,Shiro的拦截器是先与spring(Servlet)执行,两者拦截器对于URI模式匹配的差异,导致Shiro拦截器的绕过,而Shiro对其进行了两次修复,其第一次在shiro1.4.2版本出现漏洞后为删除requestURI后面的/号进行URL路径匹配,算是简单的修复了添加/号绕过的方式,而后在1.5.2版本中通过requestURI自主拼接的方式修复了/fdsf;/../hello/1/等使用了;号方式的绕过

0x05  修复方案

1.升级1.5.2版本及以上
在shiro1.5.2版本已加入的过滤器规则:
 @Test
    void testGetRequestUriWithServlet() {

        dotTestGetPathWithinApplicationFromRequest("/", "/servlet", "/foobar", "/servlet/foobar")
        dotTestGetPathWithinApplicationFromRequest("", "/servlet", "/foobar", "/servlet/foobar")
        dotTestGetPathWithinApplicationFromRequest("", "servlet", "/foobar", "/servlet/foobar")
        dotTestGetPathWithinApplicationFromRequest("/", "servlet", "/foobar", "/servlet/foobar")
        dotTestGetPathWithinApplicationFromRequest("//", "servlet", "/foobar", "/servlet/foobar")
        dotTestGetPathWithinApplicationFromRequest("//", "//servlet", "//foobar", "/servlet/foobar")
        dotTestGetPathWithinApplicationFromRequest("/context-path", "/servlet", "/foobar", "/servlet/foobar")
        dotTestGetPathWithinApplicationFromRequest("//context-path", "//servlet", "//foobar", "/servlet/foobar")
        dotTestGetPathWithinApplicationFromRequest("//context-path", "/servlet", "/../servlet/other", "/servlet/other")
        dotTestGetPathWithinApplicationFromRequest("//context-path", "/asdf", "/../servlet/other", "/servlet/other")
        dotTestGetPathWithinApplicationFromRequest("//context-path", "/asdf", ";/../servlet/other", "/asdf")
        dotTestGetPathWithinApplicationFromRequest("/context%2525path", "/servlet", "/foobar", "/servlet/foobar")
        dotTestGetPathWithinApplicationFromRequest("/c%6Fntext%20path", "/servlet", "/foobar", "/servlet/foobar")
        dotTestGetPathWithinApplicationFromRequest("/context path", "/servlet", "/foobar", "/servlet/foobar")
        dotTestGetPathWithinApplicationFromRequest("", null, null, "/")
        dotTestGetPathWithinApplicationFromRequest("", "index.jsp", null, "/index.jsp")
    }

2.尽量避免使用
*通配符作为动态路由拦截器的URL路径表达式。

0x06  参考文献

https://paper.seebug.org/1196/
https://xz.aliyun.com/t/8281

posted @ 2020-11-29 04:03  渗透测试中心  阅读(4592)  评论(0编辑  收藏  举报