shiro权限绕过漏洞分析

​ 之前协助渗透做审计的时候遇到了shiro版本不高,当时想到了shiro的权限绕过漏洞,但是在网上试了好几个payload都没成功 ,现在空下来了分析下shiro权限绕过的原理及触发条件。

为什么会导致权限绕过?

​ 首先观察一下shiro权限绕过的payload为/xx/..;/,并且在官方也强调了是和spring配合使用时出现的问题,不难判断出出现这个问题的原因在于shiro和spring处理url的不一致导致的,而spring在请求处理过程中会去除/./和/;xxxasxdasd/中的内容,通过这种方式变形的url可能和shiro配置的filter不一致,导致了权限绕过。

shiro的认证流程是怎样的?

​ 既然所有的权限绕过的漏洞都是在认证过程中导致的bypass,所有要了解bypass权限认证的关键点在于了解shiro的认证流程。通过大概入门了shiro的流程,发现其实shiro本质上是通过配置filter来对用户请求的url进行认证的。

​ 简单配置一个shiro的环境,其中关于Shiro filter的配置是在ShiroFilterFactoryBean中,可以看到这里将拦截的url放到一个map中,通过setFilterChainDefinitionMap设置到filterChainDefinitionMap属性中。

image-20210827152142957

image-20210827152630930

​ ShiroFilterFactoryBean实现了FactoryBean接口,在spring容器启动时会调用getObject获取对应的bean对象。在ShiroFilterFactoryBean中,通过createInstance创建Bean对象。

image-20210827153707698

​ 在createInstance中首先获取SecurityManager对象,这个对象是shiro的核心管理器,所有的安全相关的操作都是通过SecurityManager执行的。由于我们在配置ShiroFilterFactoryBean是已经创建了SecurityManager对象,所以这里是不为null的。下来创建了FilterChainManager,FilterChainManager中维护了拦截器链。

image-20210827154125758

​ 在org.apache.shiro.spring.web.ShiroFilterFactoryBean#createFilterChainManager中,首先通过DefaultFilterChainManager创建默认过滤器。

image-20210827154642267

image-20210827154739666

image-20210827154759289

image-20210827154806657

​ 下面获取了我们配置的两个过滤器,将这两个过滤器添加到过滤器链,并封装到DefaultFilterChainManager中,最终将过滤器链封装到PathMatchingFilterChainResolver中。

image-20210827155309890

image-20210827155448065

image-20210827155650530

​ 通过上面的配置,我们已经拿到了我们配置的过滤器链。现在我们看下请求时过滤器链如何工作。

​ 在org.apache.shiro.web.filter.mgt.PathMatchingFilterChainResolver#getChain中,会获取我们配置好的过滤器链,和请求的url进行匹配,匹配成功则创建一个proxy过滤器链,并将匹配到的filter放进去,匹配不到则返回空。

image-20210827160122031

image-20210827160257002

     得到FilterChain后,会调用doFilter过滤方法,也就是调用代理filter的doFilter方法,在代理filter中其实也就是循环调用匹配到的过滤器的doFilter方法。

image-20210827160523409

image-20210827160603856

CVE-2020-1957

shiro是如何导致权限绕过的?

​ 经过上面的分析,我猜测绕过的主要原因是在org.apache.shiro.web.filter.mgt.PathMatchingFilterChainResolver#getChain中绕过了url匹配的步骤。下面为了证实我的想法我使用payload进行测试并观察。当我使用/;/test/1请求时,可以看到这里拿到的请求已经发生了变化。按照上面的url请求,其实spring只会去除/;asdasdas/中的内容,并不会将后面的内容去空,而在shiro中已经将;后的内容全部去掉了,这里出现了和spring处理的不一致性。

image-20210827161116974

​ 而我们得到的requestURI是经过org.apache.shiro.web.filter.mgt.PathMatchingFilterChainResolver#getPathWithinApplication处理得到的。其中url获取的部分在WebUtils.getPathWithinApplication,而经过getRequestUri的处理后uri变成了/,所以问题主要出在这个方法中。

image-20210827162231558

image-20210827162418720

image-20210827162357888

​ 获取到uri后经过了decodeAndCleanUriString和normalize的处理。在decodeAndCleanUriString中首先对url请求解码,解码后再提取;前的内容作为url,这也是;截断请求的原因。

image-20210827162557664

image-20210827163326846

​ 而normalize中会对请求中的/./或者//或/../进行替换,这也是为什么在shiro中无法通过/./绕过的原因。

image-20210827163529708

​ 上面我们分析的是shiro是如何处理uri的,但是这个权限绕过还有一个前提,就是spring在去请求具体的controller时,请求的不是shiro处理后的uri。经过调试可以看到org.springframework.web.util.UrlPathHelper#getRequestUri中spring并没有获取shiro处理后的url做匹配。接下来spring会通过移除/;/的内容,并导致我们可以请求到/test/1这个路由。

image-20210827164609076

还有没有其他绕过方法?

1./.;/test/1

​ 这个url会被shiro当作/.识别,在spring中经过decodeAndCleanUriString处理后会变成/./test/1,再通过org.springframework.web.util.UrlPathHelper#getServletPath处理,会变成/test/1,因此这种变形也可以实现。

/..;/test/1无法绕过,直接使用这种方式在tomcat处理时就会报错。

image-20210827171047564

  1. /aaa/..;/test/1

    ​ 这个url经过shiro处理后,会变成/aaa/..,当然匹配不到任何过滤器,在spring中经过处理会变成/aaa/../test/1,最后经过getServletPath处理后转成/test/1最终执行请求。

绕过的条件?

​ 虽然看上去很多配置都可以绕过,但是其实和shiro的config配置有关,经过上面的分析,我们的能绕过的请求在shiro处理后会变成下面几种形式。

1
2
3
1. /
2. /.
3. /xxx/..

​ 如果在使用shiro时,做了这样的配置map.put("/**", "authc");,那么无论怎么接收的url是什么样的,也会被shiro的过滤器匹配到,所以这么配置的shiro是无法绕过的。

如何修复这个漏洞?

​ 在高版本中,主要修复代码如下:

image-20210827173349412

​ 这里不再直接对用户传入的url进行处理了,而是通过getServletPath处理后再经过removeSemicolon的处理。getServletPath是通过request.getServletPath获取的。

image-20210827173514501

​ 而之前分析请求绕过,其实spring最终决定哪个url匹配是通过getServletPath获取的,也是通过request.getServletPath获取的。这里就保证了处理的url的一致性,也就没办法绕过了。

image-20210827173709263

image-20210827173732079

CVE-2020-11989

为什么可以通过这种方式绕过?

​ 通过上一个权限绕过的分析,想要绕过shiro的权限认证,其实本质上也是绕过了filter的正则匹配。这个洞本质上来讲也是绕过了权限匹配。首先,根据https://xlab.tencent.com/cn/2020/06/30/xlab-20-002/的描述,只有在满足特定的条件下,才能导致权限绕过的漏洞。

1
2
1. 使用map.put("/hello/*", "authc"); 而不是map.put("/hello/**", "authc");
2. 只能使用@PathVariable的方式接收请求。

​ 首先了解下在shiro中配置*和**的区别

1
2
3
*表示/后的一个或多个字符串,也就是`/hello/aa/x`这种是匹配不到的

**匹配0个或者多个路径,`/hello/aa/x`可以正常匹配

​ 下面看下使用/hello/*是怎么进行路径匹配的,匹配路由的逻辑还是在org.apache.shiro.web.filter.mgt.PathMatchingFilterChainResolver#getChain中。

image-20210830112915448

​ 其中主要在org.apache.shiro.util.AntPathMatcher#doMatch中完成匹配,通过/将路径分割,并将分割后的路径分组后依次匹配,匹配成功则增加pattIdxStart计数,否则直接return fasle结束。

image-20210830114802113

​ 下面有个比较重要的判断,当pathIdxStartpattIdxEnd不一致时,也会返回false。

image-20210830115145893

​ 比如当我请求/hello/aaaa/111时,就会导致这种不一致。

image-20210830115729159

​ 虽然使用/hello/aa/11这种语法可以绕过shiro的过滤器,但是同样不会被Spring匹配到,因为spring这里只使用了一个参数。

image-20210830115817072

​ 所以有没有什么方法可以让shiro在匹配时当作两个参数,而在spring接收时当作一个。我们知道spring在处理时的url是和request.getServletPath()对应的路径是一致的。而在修复方案中,确实已经通过接收request.getServletPath()来当作shiro的处理路径了,但是这里有个问题,就是shiro在接收到request.getServletPath()的路径后还通过decodeAndCleanUriString进行了一次url解码。所以只要让request.getServletPath()接收到的是url编码后的值就可以产生不一致导致到绕过。

image-20210830120454769

​ payload如下:/hello/a%25%32%66a

image-20210830120952394

为什么只能使用@PathVariable的方式绕过?

​ 虽然使用上面的方式确实绕过了shiro的权限认证,但是我们想想,spring在处理请求时还是通过request.getServletPath()的结果处理的,而如果我们使用编码的方式绕过,spring最终接收到的url是/hello/a%2fa,其中包含了一个url编码后的值,那么如果我们使用@RequestMapping指定值xxx来接收,一定是匹配不到任何路由的。所以在/hello后只能是一个变量,所以这也就是只能使用@PathVariable的原因。而且由于我们接收的内容中有url编码后的内容,后端如果拿到这个内容进行处理会报错,所以如果想控制这个@PathVariable的内容来做某些操作也是不行的,所以这个漏洞虽然是一个CVE,但是在实际利用时是没什么用的,除非通过@PathVariable接收到内容后不使用,但是这与开发者使用@PathVariable来接收内容的本意肯定是不符的。

image-20210830122047766

如何修复这个漏洞?

​ Shiro在1.5.3版本在获取到servletPath后不再使用url解码。

image-20210830122328775

image-20210830122417293

CVE-2020-13933

为什么可以绕过?

1
/hello/%3ba

​ 根据之前的经验,绕过的还是org.apache.shiro.web.filter.mgt.PathMatchingFilterChainResolver#getChain的匹配。这里可以看到我们的输入被处理为/hello,这里根据CVE-2020-11989的经验,这里会由于pathIdxStartpattIdxEnd不一致而导致匹配不上。

image-20210830150721752

​ 而在request.getServletPath获取的路径仍然是/hello/;a,这里又导致了解析url不一致的问题。

image-20210830151104307

为什么请求会转为/hello?

​ 在获取servletpath后,会通过removeSemicolon移除;后的内容,所以此时url变为/hello/

image-20210830151357783

​ 在getChains方法中,当匹配url以/结尾,会去除末尾的/。

image-20210830151618993

漏洞利用条件是什么?

​ 通过上面的分析,上述利用方式是CVE-2020-11989的变形,所以利用条件还是需要通配符使用*而不是**,通过下面的demo可以证明。

image-20210830153207009

​ 同理,也需要@PathVariable,;也会被带入到变量中,想要通过控制变量的内容深入利用是不行的。

image-20210830154607102

为什么需要对;进行url编码?

​ 通过上面的分析,我们即使不给;编码,同样可以绕过shiro的匹配,那么为什么要给;进行url编码呢?

image-20210831133945380

image-20210831134020818

​ 下面我们回顾下SpringMVC的处理流程,当发起一个请求到达SpringMVC,首先会经过DispatcherServlet#doDispatch,由this.getHandler(processedRequest);获取对应的Handler,再通过Handler.handle方法处理请求,最后将返回的ModelAndView对象交给视图解析器解析后返回。

​ 所以请求被哪个Handler处理取决于如何根据URL匹配到对应Handler的,这个过程在getHandler中执行,handlerMappings包含多个HandlerMapping,SpringMVC会循环调用HandlerMapping.getHandler方法,直到获取到一个handler。

image-20210831141511815

​ 而我们通过@RequestMapping注册的方法是由RequestMappingHandlerMapping来处理的,所以我们主要关注RequestMappingHandlerMapping.getHandler方法,由于这个类没有getHandler方法, 所以会执行父类AbstractHandlerMapping.getHandler方法,在这个方法中,通过getHandlerInternal获取handler。

image-20210831142024408

AbstractHandlerMethodMapping#getHandlerInternal首先获取requestPath,再调用lookupHandlerMethod查找path对应的HadlerMethod,在springMVC启动时,会将@RequestMapping修饰的方法注册为HandlerMethod。

image-20210831142355307

lookupHandlerMethod还有一些复杂的方法调用,最终交给org.springframework.web.servlet.mvc.condition.PatternsRequestCondition#getMatchingPatterns完成请求url和RequestMapping 设置的请求路径的匹配。

image-20210831143300867

​ 当;没有编码时,Spring在处理过程中会将;及其之后的内容替换为空,所以得到的lookupPath为/hello/,这是匹配不到/hello/{name}的。image-20210831143516034

​ 而当;编码后,这里得到的lookupPath为/hello/;test是可以匹配到的,才能交给对应的方法处理。

image-20210831143643479

如何修复这个漏洞?

​ 升级shiro到1.6.0版本,在这个版本,新增了默认的Filter,这个Filter的拦截范围为/**,在org.apache.shiro.spring.web.ShiroFilterFactoryBean#createFilterChainManager中可以看到对应的代码。

image-20210831151524382

​ 继续跟进,发现将/**交给InvalidRequestFilter进行处理。

image-20210831153213504

​ 新增了默认Filter后,无论我们怎么绕过一定会被/**匹配到。

image-20210831152150751

​ 在InvalidRequestFilter的doFilter方法执行前,先调用onPreHandle做一些前置处理,通过isAccessAllowed方法对请求验证。

image-20210831153632212

​ 在isAccessAllowed中验证请求内容是否包含黑名单中的内容。

image-20210831153954322

总结

​ Shiro的权限认证漏洞归根结底还是和Spring会处理/;xxx的内容有关,通过shiro过滤器和Spring获取请求URL的不一致性产生了绕过。从漏洞利用的角度来讲,只有CVE-2020-1957影响最广,利用条件是不能配置map.put("/**", "authc");,也就是说即使版本存在漏洞,也可以通过配置map.put("/**", "authc");来防御权限绕过漏洞。至于其余两个绕过则显得比较鸡肋,即使绕过了Shiro的权限认证,也无法有效的控制输入。