Shiro 权限绕过学习

0x00 前言

在看 java web 审计的文章发现在其中有介绍关于 Spring 与 Shiro 之间权限绕过的问题,正好之前没有学习过,所以趁着机会学习一下

0x01 漏洞环境

这里可以在之前 Shiro 内存马注入环境的基础上进行一些简单修改

链接:https://github.com/KpLi0rn/ShiroVulnEnv

首先将 pom.xml 中对 shiro 的版本进行修改

    <dependency>
      <groupId>org.apache.shiro</groupId>
      <artifactId>shiro-web</artifactId>
      <version>1.5.2</version>
    </dependency>

    <dependency>
      <groupId>org.apache.shiro</groupId>
      <artifactId>shiro-spring</artifactId>
      <version>1.5.2</version>
    </dependency>

0x02 前置知识

下图红框处是 Shiro 的 URL 匹配规则

image-20210625200011421

匹配规则

Shiro 中的匹配规则是通过 AntPathMatcher 来进行实现的

?  匹配一个字符
*  匹配一个或多个字符
** 匹配一个或多个目录

0x01 CVE-2020-11989

漏洞产生的原因是因为 Spring 与 Shiro 之间对 url 的处理不同从而导致权限绕过

利用条件

  • Apache Shiro <= 1.5.2
  • Spring 框架中只使用 Shiro 鉴权
  • 需要后端特定的格式才可进行触发
    • 即:Shiro权限配置必须为 /xxxx/* ,同时后端逻辑必须是 /xxx/{variable} 且 variable 的类型必须是 String

漏洞环境

在上文的环境基础下,在 Shiro 包下的 ShiroConfig 中,添加红框处代码

map.put("/admin/*","authc"); 即当我们访问 /admin/xxxx 的路径的时候 Shiro 会对其进行权限校验

ps:这里的规则是 /admin/* 所以 Shiro 并不会对多个目录进行权限校验,例如:/admin/aaa/bbb 这种是不会对其进行权限校验的

image-20210625200811836

然后在 UserController 中添加如下代码

这里一定要 /xxxx/{} 的形式,且参数为 String

@ResponseBody
    @GetMapping("/admin/{name}")
    public String namePage(@PathVariable String name){  
        return "Hello" + name;
    }

image-20210625200955677

漏洞演示

正常访问 /admin/demo 的时候,由于 Shiro 的权限校验,从而会跳转到 /login 处

image-20210625201211800

当我们访问 /admin/Hello%252fBKpLi0rn 时候发现绕过了权限校验

image-20210625201101781

从访问的路由可以很容易的看出来主要是因为 %252f ,也就是两次 URL 解码之后的 /

漏洞分析

通过打断点发现我们的请求会先经过 Shiro 然后再到 Spring中

image-20210625202357193

Shiro层

在 GetMapping 处打断点,开始 debug

image-20210625202638227

通过上面断点的位置,我们可以直接定位到 Shiro 处理请求的位置,WebUtils#getPathWithinApplication

image-20210625202830018

在 getRequestUri 函数中会对我们的 uri 进行处理,跟进该函数

发现如果不为空的话就会将我们的 uri 传入 decodeAndCleanUriString 函数

ps: 中间件收到我们的 get 请求会先进行一次url解码,所以这里 Shiro 收到的是 Hello%2fBLi0rn

image-20210625203106165

来到 decodeAndCleanUriString 函数,在该函数中将 uri 传入了decodeRequestString 函数中进行 uri 解码,跟进该函数

image-20210625203238181

在 decodeRequestString 函数中会对 uri 进行一次 URL 解码

image-20210625204523920

将解码之后的 uri 进行赋值,然后会判断其中是否含有分号,如果有的话就截取分号前的内容进行返回

image-20210625205402642

这么做应该主要是为了应对如下这种情况:

http://www.xxxx.com/xxxx;jession=xxxxxx

然后将处理好的 uri 传递给了 normalize 函数

image-20210625205745371

在 normalize 函数中会对 uri 进行一些处理

'\\' => '/'

'/./ => '/'

/../ 前面的内容和后面进行拼接

处理完之后进行返回

image-20210625210342774

将返回赋到 requestUri

image-20210625210442323

继续跟下去知道来到 PathMatchingFilterChainResolver#getChain 中,在该函数中会获取我们在 ShiroConfig 中的规则,调用 pathMatches 函数来进行匹配,跟进 pathMatches 函数

image-20210625211425054

在 pathMatches 函数中可发现匹配是通过 AntPathMatcher 来实现的 ,跟进 matches 方法

image-20210625211532309

最终调用了 AntPathMatcher#doMatch 方法

image-20210625211716145

在 doMatch 中实现匹配

这里我们的规则是 /admin/*

但是我们此时的 path 为 /admin/Hello/Bli0rn

由于没有匹配成功,所以返回 false

image-20210625225404293

最后回到 getChain 函数,由于规则都遍历了没有发现匹配的,就返回 null,至此 Shiro 的权限就绕过了

image-20210625230907103

由于 getChain 中返回的是 null,所以这里的 resolved 也是 null

image-20210626152850287

由于 resolved 为 null,只会返回默认的 ApplicationFilterChain,在默认的 ApplicationFilterChain 中是没有任何权限校验

image-20210626153056009

至此 Shiro 层面的权限就成功绕过了

题外话

如果是正常的拦截情况的话,会返回 ProxiedFilterChain,即先走 Shiro 自身的 Filter,然后再委托给 Servlet 容器的 FilterChain 进行 Servlet 容器级别的 Filter 链执行

image-20210626154917364

image-20210626155216927

image-20210626155110414

Spring 层

文章链接:http://www.51gjie.com/javaweb/921.html,https://www.anquanke.com/post/id/218270#h3-7

熟悉 Spring 的师傅应该都知道在 Spring 中 DispatcherServlet 是负责请求派发的,即将将对应的请求转发到对应的 Controller 来处理,其一个主要的作用是通过 HandlerMapping 将请求映射到处理器。在处理过程中会调用 getHandler 方法来获取一个可以处理该请求的Handler

org.springframework.web.servlet.DispatcherServlet#doDispatch 大约484行左右 调用了 getHandler 方法

image-20210628090429117

在本函数内会遍历所有已加载的 handlerMappings ,通过调用HandlerMapping的getHandler方法来进行判断是否这个Handler可以处理当前请求

image-20210628090622606

发现在 getHandler 中调用了 getHandlerInternal 函数,跟进该函数

image-20210628090654613

在 getHandlerInternal 中,调用了 getUrlPathHelper().getLookupPathForRequest(request) 该方法会根据请求解析出具体的用于匹配Handler的url,这是一个很关键的步骤,寻找合适的Handler就是根据url来进行的

image-20210627232023438

在 getLookupPathForRequest 函数中调用了 getPathWithinServletMapping 然后赋给了 rest ,如果 rest 为 "" 那么就调用 getPathWithinApplication 来根据我们传入的 request 获取 应用内的路径

image-20210627221027051

getPathWithinServletMapping:返回给定请求的Servlet映射中的路径,即请求 url 中超出调用 Servlet 的部分,在官方文档中给出了 demo

image-20210627220337971

跟进 getPathWithinApplication 函数,发现会调用 getRequestUri 来获取我们的 requestUri ,跟进该函数

image-20210627221231019

发现首先会从上下文的 javax.servlet.include.request_uri 属性中获取,如果为 null 则调用 request.getRequestURI() 获取到我们的 uri,然后通过 decodeAndCleanUriString 进行了一次 url 解码

image-20210627221503243

/admin/Hello%252fBKpLi0rn 变为了 /admin/Hello%2fBKpLi0rn

image-20210627221919188

重新回到 getHandlerInternal 函数,可以看到 lookupPath 和 request 传入了 lookupHandlerMethod 函数中

这里的 lookupPath 其实就是获取到了我们请求对应的 uri,接下来就可以根据lookupPath来匹配Controller的Handler了

image-20210627232202063

Spring 获取路径映射

参考文章:https://www.jianshu.com/p/1136212b9197

matches 会存储所有匹配到的方法,如果matches为空就进入该判断,调用 addMatchingMappings 函数来添加匹配的Handler

image-20210627234151151

继续跟进 getMatchingMapping 函数

image-20210627234511552

进行了一系列的判断,跟进 getMatchingCondition

image-20210627234755802

到 getMatchingCondition 函数,可以看到 lookupPath 传入了 getMatchingPatterns 函数,跟进该函数

image-20210627222641917

getMatchingPatterns 函数中会将我们的请求和配置的 url 进行比较,匹配成功就添加到 matches 中

image-20210627214639602

上面的这些功能都是为了在所有匹配的Handler之后需要挑选一个最合适的Handler进行请求的处理,获取到合适的 Handler 之后就进行Handler的访问来处理请求了(后面就不跟了 Orz

最终

image-20210626163009707

漏洞修复

在 Shiro 1.5.3 版本中对 getPathWithinApplication 进行了修改,取消了 url 解码的函数,所以我们这里的 uri 并不会被完全解码

image-20210626163700010

0x02 CVE-2020-1957

其实应该是这个写在最前面的,但是发现的时候 11989 已经已经写完了 Orz

利用条件

  • Apache Shiro <= 1.5.1
  • Spring 框架中只使用 Shiro 鉴权

漏洞环境

修改 pom.xml 中的版本为 2.1.5.RELEASE

ps:之前的 2.4.5 复现失败了,会报404,换成 2.1.5 就可以了

image-20210627132207537

修改 shiro 版本为 1.5.1

image-20210627132301012

在 UserController 中添加如下代码

   @ResponseBody
    @RequestMapping(value = "/admin/index", method = RequestMethod.GET)
    public String admin() {
        return "admin secret bypass and unauthorized access";
    }

        @ResponseBody
    @RequestMapping(value = "/demo", method = RequestMethod.GET)
    public String demo() {
        return "demo";
    }

在 ShiroConfig 中变为如下规则

map.put("/doLogin", "anon");
map.put("/demo/**","anon");
map.put("/unauth", "user");
map.put("/admin/**","authc");
map.put("/**", "authc");

image-20210627132450063

漏洞演示

在本实验 demo 中访问 /demo 是不需要权限的,但是访问 /admin/index 时会被 Shiro 进行验权从而跳转到 /login

但是通过 /demo/..;/admin/index 就可以绕过 shiro 的权限来访问到 /admin/index

可以看到成功绕过 shiro 进行权限校验

image-20210627132505498

漏洞分析

在 getPathWithinApp 中调用了 getRequestUri 中获取我们请求的 uri

image-20210627164307915

在 getRequestUri 中会调用 decodeAndCleanUriString

image-20210627165607208

在 decodeAndCleanUriString 处,会获取 uri 中分号的索引,如果 uri 中存在分号那么就会截取分号前的字符串

image-20210627133929734

后面会来到 PathMatchingFilterChainResolver#getChain 中进行权限匹配,此时我们的 requestURI 为 demo/.. 由于我们 Shiro 的规则为 /demo/** anon ,因此校验通过

image-20210627135009773

image-20210627134703094

所以 Shiro 这部分的绕过其实就是因为截取了分号前面,也就是我们这里的 /demo/.. 然后和 /demo/ 匹配上了,由于我们的 /demo/ 是没有任何权限限制的,因此就绕过了

在实际情况中应该 login 多一些,例如 /login/..;/admin/index

这样 Shiro 部分的权限就绕过了

后面就是 Spring 的部分

和上文一样我们来到了 getHandler 函数处

image-20210628095156107

跟进 getHandlerInternal 函数

image-20210628095225033

在 getHandlerInternal 函数中会调用 getLookupPathForRequest 来根据我们的请求返回对应的 uri

image-20210628095311870

在 getLookupPathForRequest 函数中调用了 getPathWithinServletMapping 来获取请求的相对路径,跟进该方法

image-20210627172357133

getPathWithinServletMapping:返回给定请求的Servlet映射中的路径,即请求 url 中超出调用 Servlet 的部分,在官方文档中给出了 demo

image-20210627220337971

在 getPathWithinApplication 函数中调用了 getServletPath,获取请求对应的 Servlet 的路径

其实该方法中就是具体的对请求的url的处理

image-20210627183506858

在 getServletPath 函数中,首先会从上下文中进行获取,如果获取结果为 null 就会调用 request.getServletPath 即返回请求的URL中调用Servlet的部分

image-20210627184132218

我们这里跟进一下 getServletPath 函数,继续跟进

image-20210627185113465

可以看到返回了 this.mappingData.wrapperPath 也就是 /admin/index (即 Tomcat 中 servlet-path 匹配后的结果)

http://dengchengchao.com/?p=1065

image-20210627191756350

最后返回给 springboot

image-20210627191842902

漏洞修复

https://github.com/apache/shiro/commit/3708d7907016bf2fa12691dff6ff0def1249b8ce#diff-98f7bc5c0391389e56531f8b3754081aR139

将原先的 request.getRequestURI() 替换成了 getContextPath() 、getServletPath() 、getPathInfo() 的组合,这样就能获取我们想要的了,从而避免因为获取差异性而导致绕过,这样就与返回给 springboot 的路径保持一致了

image-20210627194029641

0x03 CVE-2020-13933

利用条件

  • Apache Shiro < 1.6.0
  • Spring 框架中只使用 Shiro 鉴权
  • 需要后端特定的格式才可进行触发

    • 即:Shiro权限配置必须为 /xxxx/* ,同时后端逻辑必须是 /xxx/{variable} 且 variable 的类型必须是 String

漏洞环境

同 CVE-2020-11989 环境,只要将版本改为 1.5.3 即可,修改 pom.xml

漏洞演示

/admin/%3BKpLi0rn

image-20210626222417420

漏洞分析

在 1.5.3 版本之后,Shiro 不会进行 url 的二次解码,但是在 removeSemicolon 中仍存在绕过的可能性

跟进该函数

image-20210626222646996

removeSemicolon 函数中,会获取第一次出现分号的索引,然后截取分号前的 uri

这种情况的处理应该是为了应对 www.xxxx.com/admin;jession=asfoasdo 这种情况

image-20210626222813551

所以我们只要利用 /admin/;whatever 这样的结构就可以绕过 Shiro 的权限校验

红框处会将 uri 最后的 / 进行去除,所以此时的 requestURI 为 /admin 自然是不符合我们这里的 shiro 拦截规则的 /admin/*

image-20210626223211931

然后在 Spring 中则是会将后面部分当作是参数进行获取从而输出

image-20210626223522873

漏洞修复

https://github.com/apache/shiro/commit/dc194fc977ab6cfbf3c1ecb085e2bac5db14af6d

增加了 InvalidRequestFilter 类来对一些特殊情况进行处理

image-20210626224748697

遇到特殊字符会直接报错

image-20210626224718609

同时增加了 /** 的规则,来防止一些匹配不到的情况

image-20210626224907536

0x04 CVE-2020-17523

利用条件

  • Apache Shiro < 1.7.1

  • Spring 框架中只使用 Shiro 鉴权

  • 需要后端特定的格式才可进行触发

    • 即:Shiro权限配置必须为 /xxxx/* ,同时后端逻辑必须是 /xxx/{variable} 且 variable 的类型必须是 String

漏洞环境

同 CVE-2020-11989 环境,pom.xml 中版本修改为 1.7.0 或及以下即可

漏洞演示

image-20210626225150306

漏洞分析

该绕过发生在 AntPathMatcher#doMatch 中,其中利用了 tokenizeToStringArray 函数分割传入的 uri 为数值,跟进该函数

image-20210626230110945

在该函数中会调用 trim 将我们的空格给去掉,从而导致 /admin/ 与 /admin/* 不匹配,因此绕过 shiro 权限校验

image-20210626230258227

漏洞修复

增加了选项,tokenizeToStringArray 函数中默认不会进行 trim

image-20210626232042464

结合上图传入的微 false,因此默认情况下 token = token.trim() 并不会被执行

image-20210626232018516

0x05 总结

整体学习下来其实根本问题就是 Shiro 与 Spring 对请求处理不同从而导致我们可以构造特殊的 uri 从而绕过 Shiro 的权限校验

0x06 参考链接

https://hpdoger.cn/2021/02/08/title:%20Shiro%E6%9D%83%E9%99%90%E7%BB%95%E8%BF%87%E6%B1%87%E6%80%BB/

https://www.anquanke.com/post/id/218270#h3-7

http://www.lmxspace.com/2020/08/24/Apache-shiro-%E6%9D%83%E9%99%90%E7%BB%95%E8%BF%87%E6%BC%8F%E6%B4%9E%E6%B1%87%E6%80%BB/#1%E3%80%81CVE-2020-1957

https://www.jianshu.com/p/1136212b9197

http://www.51gjie.com/javaweb/911.html

https://xz.aliyun.com/t/8281#toc-5

点赞

发表评论

电子邮件地址不会被公开。必填项已用 * 标注