Shiro权限绕过合集
Shiro-682 : Shiro < 1.5.0权限绕过
环境搭建
https://github.com/lenve/javaboy-code-samples/tree/master/shiro/shiro-basic
在LoginController里添加如下代码
1 | @GetMapping("/admin/{currentPage}") |
在ShiroConfig里面设置Shiro拦截器,添加一行map.put("/admin/*", "authc");
漏洞复现
当访问admin下的内容时需要认证
再URL后面添加一个反斜杠成功绕过认证访问
漏洞分析
Shiro解析部分
Shiro对URL的获取以及匹配拦截器对应的方法如下:
接下来进入while循环判断
这里的requestURL即为请求时的URL,即/admin/1
,pathPattern为/doLogin
,将这两个参数代入pathMatches方法,跟进
跟进doMatch,代码量有点多,直接看到关键代码部分
这里return的意思是判断pattern是否是以/
结尾,如果为True则返回path是否以/
结尾的值,如果为False则返回path是否以/
结尾的值的相反值。这里pattern值为/admin/*
不是以/
结尾,所以返回path是否以/
结尾的值的相反值,path值为/admin/1/`所以返回False。
于是回到之前的do-while循环那儿
因为返回False,所以while里面的值为True,于是就能通过验证进入到do里面
这里我们访问通过使用/admin/1
来看看二者区别,直接看到doMatch里面的关键代码
pattern是我们写定了的不允许访问的路径所以不变,唯一变的是path少了一个\
,也就是我们只需要看!path.endsWith(this.patheparator)
的值如何变化,现在path值为/admin/1
不以\
结尾,所以为False,又因为是相反值所以返回True,在while循环那儿就就为False跳出循环。
也就是被过滤器检测到
Spring解析部分
从DispatcherServlet#getHandler开始,从var2中取出一个值作为mapping
接着调用mapping.getHandler,第一次这里得到的handler为空,就不去跟进了,给出第一次调用流程
1 | DispatcherServlet#getHandler |
第二次回到while循环,此时mapping值为RequestMappingHandlerMapping
跟进AbstractHandlerMapping#getHandler
跟进AbstractHandlerMethodMapping#getHandlerInternal
跟进AbstractHandlerMethodMapping#lookupHandlerMethod
跟进AbstractHandlerMethodMapping#addMatchingMappings
跟进RequestMappingInfoHandlerMapping#getMatchingMapping
跟进RequestMappingInfo#getMatchingCondition
跟进PatternsRequestCondition#getMatchingCondition
跟进PatternsRequestCondition#getMatchingPatterns
跟进PatternsRequestCondition#getMatchingPattern
关键点就在这里,三个条件都为True,所以返回pattern+/
,即/admin/1/
可以看出,漏洞的成因在于Shiro与Spring解析的差异性,Shiro中的/admin/*
不能匹配到/admin/1/
,而Spring控制器/admin/1
能够被/admin/1/
调用,所以就导致了该漏洞
漏洞修复
去除了requestURI和pathPattern末尾的斜杠
CVE-2020-1957 : Shiro < 1.5.2权限绕过
漏洞复现
漏洞分析
Shiro部分
还是一样定位到getChain方法
跟进PathMatchingFilterChainResolver#getPathWithinApplication
跟进WebUtils#getPathWithinApplication
跟进WebUtils#getRequestUri
这里获取到了我们访问的uri,跟进WebUtils#ecodeAndCleanUriString
这里的semicolonIndex为0,字符串截取从0到0,即为空,回到WebUtils#getRequestUri,跟进WebUtils#normalize
继续跟进
因为这里的normalized就是前面path的值,也就是为空,这里加上/
,所以normalized现在值为/
这里返回值为/
,接着就和上一个洞一样进入while循环遍历pattern来匹配,没有匹配到/
是被过滤了的,最后因为pattern已经遍历完成功绕过Shiro的检测
Spring部分
还是一样从DispatcherServlet#getHandler并且经过一次循环后开始
一路跟到UrlPathHelper#getRequestUri
跟进UrlPathHelper#decodeAndCleanUriString
这里的removeSemicolonContent方法会反斜杠为分割,将分号后的内容进行删除
1 | ;/admin/1 => /admin/1 |
所以返回的是/admin/1
这样也就能正确匹配路由了,就导致了权限绕过
漏洞修复
将1.5.2的代码与当前版本进行diff,可以发现对应的patch如下:
1 | public static String getRequestUri(HttpServletRequest request) { |
它将 HttpServletRequest#getRequestUri 获取路由修改为了通过
ContextPath 、 ServletPath 、 PathInfo 三者拼接的方式获取路由,由于 ServletPath 能够正确的处理分号,通过这种方式来获取对应的路由能够成功修复此漏洞。
CVE-2020-13933 : Shiro<1.6.0权限绕过
漏洞复现
漏洞分析
Shiro部分
Shiro对URL部分的处理:
1 | public static String getPathWithinApplication(HttpServletRequest request) { |
Shiro使用下面两者拼接的方式获取URL:
1 | getServletPath(request) + getPathInfo(request) |
getServletPath默认会将URI进行Urldecode,如下:
获取到URL后会使用removeSemicolon方法进行去除分号处理
1 | private static String removeSemicolon(String uri) { |
返回值为 /admin/,获取到URI后,会判断URI末尾是否包含 /
:
1 | if (requestURI != null && !"/".equals(requestURI) && requestURI.endsWith("/")) |
在这里会将末尾的斜杠去掉,得到/admin,这样就不能和pattern(/admin/*)匹配成功,所以就绕过了Shiro的校验。
Spring部分
Spring通过 getPathWithinServletMapping 方法获取路由:
1 | public String getPathWithinServletMapping(HttpServletRequest request) { |
在 getPathWithinApplication 方法中会使用 getRequestUri 来获取对应路由:
1 | public String getRequestUri(HttpServletRequest request) { |
在这里获取到的URI是没经过decode的URL,随后会进入到 decodeAndCleanUriString方法格式化URL:
1 | private String decodeAndCleanUriString(HttpServletRequest request, String uri) |
在该方法中,首先会调用 removeSemicolonContent 对分号进行截断,此时因为分号是urlencode后 的,所以没有被匹配到,URI无任何变化。在 decodeRequestString 方法中,该方法会对URI进行 UrlDecode,所以最终获取到的路由为 /admin/;page ,该路由能够匹配上 /admin/{name} ,所以能够正常返回⻚面。
漏洞修复
增加了一个filter
1 | private static final List<String> SEMICOLON = |
该filter会对URL中的分号进行拦截。