0x00 前言

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

0x01 漏洞环境

这里可以在之前 Shiro 内存马注入环境的基础上进行一些简单修改
链接:https://github.com/KpLi0rn/ShiroVulnEnv
首先将 pom.xml 中对 shiro 的版本进行修改

org.apache.shiro
shiro-web
1.5.2

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

0x02 前置知识

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

匹配规则

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.png
      然后在 UserController 中添加如下代码
      这里一定要 /xxxx/{} 的形式,且参数为 String
      @ResponseBody
      @GetMapping(“/admin/{name}”)
      public String namePage(@PathVariable String name){
      return “Hello” + name;
      }
      image.png

      漏洞演示

      正常访问 /admin/demo 的时候,由于 Shiro 的权限校验,从而会跳转到 /login 处
      image.png
      当我们访问 /admin/Hello%252fBKpLi0rn 时候发现绕过了权限校验
      image.png
      从访问的路由可以很容易的看出来主要是因为 %252f ,也就是两次 URL 解码之后的 /

      漏洞分析

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

      Shiro层

      在 GetMapping 处打断点,开始 debug
      image.png
      通过上面断点的位置,我们可以直接定位到 Shiro 处理请求的位置,WebUtils#getPathWithinApplication
      image.png
      在 getRequestUri 函数中会对我们的 uri 进行处理,跟进该函数
      发现如果不为空的话就会将我们的 uri 传入 decodeAndCleanUriString 函数
      ps: 中间件收到我们的 get 请求会先进行一次url解码,所以这里 Shiro 收到的是 Hello%2fBLi0rn
      image.png
      来到 decodeAndCleanUriString 函数,在该函数中将 uri 传入了decodeRequestString 函数中进行 uri 解码,跟进该函数
      image.png
      在 decodeRequestString 函数中会对 uri 进行一次 URL 解码
      image.png
      将解码之后的 uri 进行赋值,然后会判断其中是否含有分号,如果有的话就截取分号前的内容进行返回
      image.png
      这么做应该主要是为了应对如下这种情况:
      http://www.xxxx.com/xxxx;jession=xxxxxx
      然后将处理好的 uri 传递给了 normalize 函数
      image.png
      在 normalize 函数中会对 uri 进行一些处理
      将 ‘\‘ => ‘/‘
      将 ‘/./ => ‘/‘
      将 /../ 前面的内容和后面进行拼接
      处理完之后进行返回
      image.png
      将返回赋到 requestUri
      image.png
      继续跟下去知道来到 PathMatchingFilterChainResolver#getChain 中,在该函数中会获取我们在 ShiroConfig 中的规则,调用 pathMatches 函数来进行匹配,跟进 pathMatches 函数
      image.png
      在 pathMatches 函数中可发现匹配是通过 AntPathMatcher 来实现的 ,跟进 matches 方法
      image.png
      最终调用了 AntPathMatcher#doMatch 方法
      image.png
      在 doMatch 中实现匹配
      这里我们的规则是 /admin/
      但是我们此时的 path 为 /admin/Hello/Bli0rn
      由于没有匹配成功,所以返回 false
      image.png
      最后回到 getChain 函数,由于规则都遍历了没有发现匹配的,就返回 null,至此 Shiro 的权限就绕过了
      image.png
      由于 getChain 中返回的是 null,所以这里的 resolved 也是 null
      image.png
      由于 resolved 为 null,只会返回默认的 ApplicationFilterChain,在默认的 ApplicationFilterChain 中是没有任何权限校验
      image.png
      至此 Shiro 层面的权限就成功绕过了
      *题外话

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

      Spring 层

      文章链接:http://www.51gjie.com/javaweb/921.htmlhttps://www.anquanke.com/post/id/218270#h3-7
      熟悉 Spring 的师傅应该都知道在 Spring 中 DispatcherServlet 是负责请求派发的,即将将对应的请求转发到对应的 Controller 来处理,其一个主要的作用是通过 HandlerMapping 将请求映射到处理器。在处理过程中会调用 getHandler 方法来获取一个可以处理该请求的Handler
      org.springframework.web.servlet.DispatcherServlet#doDispatch 大约484行左右 调用了 getHandler 方法
      image.png
      在本函数内会遍历所有已加载的 handlerMappings ,通过调用HandlerMapping的getHandler方法来进行判断是否这个Handler可以处理当前请求
      image.png
      发现在 getHandler 中调用了 getHandlerInternal 函数,跟进该函数
      image.png
      在 getHandlerInternal 中,调用了 getUrlPathHelper().getLookupPathForRequest(request) 该方法会根据请求解析出具体的用于匹配Handler的url,这是一个很关键的步骤,寻找合适的Handler就是根据url来进行的
      image.png
      在 getLookupPathForRequest 函数中调用了 getPathWithinServletMapping 然后赋给了 rest ,如果 rest 为 “” 那么就调用 getPathWithinApplication 来根据我们传入的 request 获取 应用内的路径
      image.png
      getPathWithinServletMapping:返回给定请求的Servlet映射中的路径,即请求 url 中超出调用 Servlet 的部分,在官方文档中给出了 demo
      image.png
      跟进 getPathWithinApplication 函数,发现会调用 getRequestUri 来获取我们的 requestUri ,跟进该函数
      image.png
      发现首先会从上下文的 javax.servlet.include.request_uri 属性中获取,如果为 null 则调用 request.getRequestURI() 获取到我们的 uri,然后通过 decodeAndCleanUriString 进行了一次 url 解码
      image.png
      从 /admin/Hello%252fBKpLi0rn 变为了 /admin/Hello%2fBKpLi0rn
      image.png
      重新回到 getHandlerInternal 函数,可以看到 lookupPath 和 request 传入了 lookupHandlerMethod 函数中
      这里的 lookupPath 其实就是获取到了我们请求对应的 uri,接下来就可以根据lookupPath来匹配Controller的Handler了
      image.png

      Spring 获取路径映射

      参考文章:https://www.jianshu.com/p/1136212b9197
      matches 会存储所有匹配到的方法,如果matches为空就进入该判断,调用 addMatchingMappings 函数来添加匹配的Handler
      image.png
      继续跟进 getMatchingMapping 函数
      image.png
      进行了一系列的判断,跟进 getMatchingCondition
      image.png
      到 getMatchingCondition 函数,可以看到 lookupPath 传入了 getMatchingPatterns 函数,跟进该函数
      image.png
      getMatchingPatterns 函数中会将我们的请求和配置的 url 进行比较,匹配成功就添加到 matches 中
      image.png
      上面的这些功能都是为了在所有匹配的Handler之后需要挑选一个最合适的Handler进行请求的处理,获取到合适的 Handler 之后就进行Handler的访问来处理请求了(后面就不跟了 Orz
      最终
      image.png

      漏洞修复

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

      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.png
    修改 shiro 版本为 1.5.1
    image.png
    在 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.png

    漏洞演示

    在本实验 demo 中访问 /demo 是不需要权限的,但是访问 /admin/index 时会被 Shiro 进行验权从而跳转到 /login
    但是通过 /demo/..;/admin/index 就可以绕过 shiro 的权限来访问到 /admin/index
    可以看到成功绕过 shiro 进行权限校验
    image.png

    漏洞分析

    在 getPathWithinApp 中调用了 getRequestUri 中获取我们请求的 uri
    image.png
    在 getRequestUri 中会调用 decodeAndCleanUriString
    image.png
    在 decodeAndCleanUriString 处,会获取 uri 中分号的索引,如果 uri 中存在分号那么就会截取分号前的字符串
    image.png
    后面会来到 PathMatchingFilterChainResolver#getChain 中进行权限匹配,此时我们的 requestURI 为 demo/.. 由于我们 Shiro 的规则为 /demo/ anon ,因此校验通过
    image.png
    image.png
    所以 Shiro 这部分的绕过其实就是因为截取了分号前面,也就是我们这里的 /demo/.. 然后和 /demo/
    匹配上了,由于我们的 /demo/** 是没有任何权限限制的,因此就绕过了
    在实际情况中应该 login 多一些,例如 /login/..;/admin/index
    这样 Shiro 部分的权限就绕过了
    后面就是 Spring 的部分
    和上文一样我们来到了 getHandler 函数处
    image.png
    跟进 getHandlerInternal 函数
    image.png
    在 getHandlerInternal 函数中会调用 getLookupPathForRequest 来根据我们的请求返回对应的 uri
    image.png
    在 getLookupPathForRequest 函数中调用了 getPathWithinServletMapping 来获取请求的相对路径,跟进该方法
    image.png
    getPathWithinServletMapping:返回给定请求的Servlet映射中的路径,即请求 url 中超出调用 Servlet 的部分,在官方文档中给出了 demo
    image.png
    在 getPathWithinApplication 函数中调用了 getServletPath,获取请求对应的 Servlet 的路径
    其实该方法中就是具体的对请求的url的处理
    image.png
    在 getServletPath 函数中,首先会从上下文中进行获取,如果获取结果为 null 就会调用 request.getServletPath 即返回请求的URL中调用Servlet的部分
    image.png
    我们这里跟进一下 getServletPath 函数,继续跟进
    image.png
    可以看到返回了 this.mappingData.wrapperPath 也就是 /admin/index (即 Tomcat 中 servlet-path 匹配后的结果)
    http://dengchengchao.com/?p=1065
    image.png
    最后返回给 springboot
    image.png

    漏洞修复

    https://github.com/apache/shiro/commit/3708d7907016bf2fa12691dff6ff0def1249b8ce#diff-98f7bc5c0391389e56531f8b3754081aR139
    将原先的 request.getRequestURI() 替换成了 getContextPath() 、getServletPath() 、getPathInfo() 的组合,这样就能获取我们想要的了,从而避免因为获取差异性而导致绕过,这样就与返回给 springboot 的路径保持一致了
    image.png

    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.png

      漏洞分析

      在 1.5.3 版本之后,Shiro 不会进行 url 的二次解码,但是在 removeSemicolon 中仍存在绕过的可能性
      跟进该函数
      image.png
      removeSemicolon 函数中,会获取第一次出现分号的索引,然后截取分号前的 uri
      这种情况的处理应该是为了应对 www.xxxx.com/admin;jession=asfoasdo 这种情况
      image.png
      所以我们只要利用 /admin/;whatever 这样的结构就可以绕过 Shiro 的权限校验
      红框处会将 uri 最后的 / 进行去除,所以此时的 requestURI 为 /admin 自然是不符合我们这里的 shiro 拦截规则的 /admin/*
      image.png
      然后在 Spring 中则是会将后面部分当作是参数进行获取从而输出
      image.png

      漏洞修复

      https://github.com/apache/shiro/commit/dc194fc977ab6cfbf3c1ecb085e2bac5db14af6d
      增加了 InvalidRequestFilter 类来对一些特殊情况进行处理
      image.png
      遇到特殊字符会直接报错
      image.png
      同时增加了 /** 的规则,来防止一些匹配不到的情况
      image.png

      0x04 CVE-2020-17523

      利用条件

  • Apache Shiro < 1.7.1

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