Shiro权限绕过汇总
Shiro权限绕过汇总
整体来说就是shiro与spring对uri处理差异导致的漏洞
CVE-2020-11989
影响范围
- Apache Shiro < 1.5.3
- Spring 框架中只使用 Shiro 鉴权
环境
下面三个CVE复现都是基于此源码:https://github.com/l3yx/springboot-shiro
配置tomcat+war即可。注意如果maven resolve失败的话,证明本地之前已经download过相应的依赖,需要手动到~/.m2/respository下删除
丢出来filter的逻辑,以下操作通过ant风格的语法设置了去检查在访问/admin路由之后的一级目录的用户是否有权限。
路由控制器逻辑(Spring业务层)
漏洞复现
关于Shiro自定义Reaml处理流程看笔记“shiro安全框架基础”
这里需要了解一下配置shiroConfig-Filter里的URL是ant格式,路径是支持通配符表示的
?:匹配一个字符
*:匹配零个或多个字符串
**:匹配路径中的零个或多个路径
所以未认证的用户访问/admin/xxx
的资源会被重定向到login
漏洞分析
访问/admin/xxx%252fxxx/
会进入/admin/{name}
逻辑,其中对%252f
是对/
二次url编码
当然这个场景下需要一些限制条件,首先权限ant风格的配置需要是*
而不是**
,同时controller需要接收的request参数(@PathVariable)的类型需要是String,否则将会出错。
GET请求的数据包如下
GET /srpingboot_shiro_war/admin/gss%252fe HTTP/1.1
Host: 192.168.31.101:8081
Upgrade-Insecure-Requests: 1
首先进入shiro的逻辑,WebUtils#getPathWithinApplication
处理request请求
步入getRequestUri
函数,getServletPath()
经过一次urldecode之后赋值给uri
,接着uri
会步入函数decodeAndCleanUriString
继续跟进decodeRequestString
,此时传入的实参uri为/srpingboot_shiro_war//admin/gss%2fe
其中decodeRequestString
也是对uri进行urldecode解码操作,decode后的结果如下
至此,我们在get请求传递的2次编码url已经被完全decode。下一步就是Shiro的filter对请求资源的权限校验,主要的步骤跟进AntPathMatcher.class#doMatch
path
的值是二次解码后的/admin/gss/e
,pattern
为Shiro配置中ant
格式的匹配字符串。
doMatch
函数的大致逻辑是以/
分割pattern
字符串与path
字符串,存为数组pattDirs
与pathDirs
。而后循环遍历patDirs
中的值,通过matchStrings
函数与pathDirs
数组中的值进行匹配,匹配失败会直接返回false(也就是证明没有权限问题)
如果匹配到了则会每次使pattIdxStart++
,例如这里的pattern数组分为[admin,*]
,则会依次匹配到admin
与gss
,那么pattIdxStart
为2
pattIdxEnd
是pattern数组的末尾索引,这里为1
而在之后的判断中,赋值后的pattIdxStart
>pattIdxEnd
返回false绕过了判断
也就是说,/*
这种匹配只能命中/admin/gss
这种格式,无法命中/admin/gss/xxx
。
绕过Shiro的权限判断后。进入Spring处理路由的流程如下,解码出来的路由为/admin/gss%2fe
,当然可以进入@GetMapping("/admin/{name}")
控制器中的逻辑
漏洞修复
在1.5.3版本,采用标准的 getServletPath 和 getPathInfo 进行uri处理,同时取消了url解码,这样pattIdxStart
与pattIdxEnd
就是相同的数值
public static String getPathWithinApplication(HttpServletRequest request) {
return normalize(removeSemicolon(getServletPath(request) + getPathInfo(request)));
}
CVE-2020-13933
影响范围
- Apache Shiro < 1.6.0
- Spring 框架中只使用 Shiro 鉴权
复现与分析
请求数据包如下
GET /srpingboot_shiro_war/admin/%3beee HTTP/1.1
Host: 192.168.31.101:8081
Upgrade-Insecure-Requests: 1
其实就是对11989的绕过,思路都是一样的。1.5.3修复之后不会二次解码url但是新增的removeSemicolon
函数在分割”;”的时候又产生了问题
步入removeSemicolon
发现它就是返回”;”之前的内容,实际上是为了处理GET请求中”/admin/;jessid=xxxx”这样的需求
由于此时path值为/admin
,当然不属于请求/admin/*
下的资源,因此不会被鉴权
之后进入Spring处理路由时仍匹配到/admin/;xxxx
,进入业务逻辑
漏洞修复
CVE-2020-17523
也可以是把它看作CVE-2020-13933的绕过,1.7.0以前的版本AntPathMatcher.class#doMatch
在处理空格时又又又又又把/admin/%20
trim后作为/admin
,而当作一层目录。导致/admin/
与/admin/*
不匹配
影响范围
- Apache Shiro < 1.7.1
- Spring 框架中只使用 Shiro 鉴权
复现与分析
GET /srpingboot_shiro_war/admin/%20 HTTP/1.1
Host: 192.168.31.101:8081
Upgrade-Insecure-Requests: 1
仍然跟到doMatch
,tokenizeToStringArray
函数将path
以反斜线分割为数组,这里我传入的path
值为/admin
跟进tokenizeToStringArray
函数看具体实现,对于”/“分割之后的token
进行了trim()
操作
tokenizeToStringArray
的返回值会将path
字符串分割为数组['admin']
,可见此时的空格已被去除,path
又被作为一层路径。
接下来的for
循环在之前提到过。由于path只包含一个元素,因此我们的pathIdxStart
与pathIdxEnd
初值均为0;经过一次循环后,pathIdxStart
自增为1
因此进入判断去匹配是否有**
,很明显没有则返回false
绕过权限
原理上来说trim()
会清空字符串前后所有的whitespace,空格只是其中的一种,但是在测试中发现除了空格以外的其他whitespace,例如%08、%09、%0a,spring+tomcat 处理时都会返回400
漏洞修复
增加了选项,tokenizeToStringArray
函数默认不进行trim
的操作
总结
这三个洞的精髓就在于:通过二次编码或者”;”绕过Shiro filter对/admin/*
的ant。让shiro判断时认为/admin/;sds
是一层目录,或/admin/x%252fe
认为是三层目录从而绕过权鉴。而Spring在处理这样的uri时只是把它当作了二层目录,会被/admin/{xxx}
匹配到从而分发到业务逻辑部分的代码中
当然这几种绕过有一个最大的局限性,就是Spring控制器的代码层在写路由注解时,使用通配符或者泛解模式,比如这里的/admin/{xxx}
如果路由写死为/admin/hpdoger
则无法进入Spring相应代码逻辑