Shiro越权学习2.0
2022-04-19 22:36:19

Shiro越权学习2.0

前言

之前年初看到su18大师傅对Shiro的CVE做了整理,里面的好几个漏洞都没有调过,写这篇文章记录自己的学习过程。可能有些地方有问题还请师傅们指教。

CVE-2020-11989

https://issues.apache.org/jira/browse/SHIRO-782

影响范围:shiro < 1.5.3

原理

ContextPath

前置条件

  1. 必须配置有context-path
  2. Spring控制器中没有另外的权限校验代码

参考作者文章

这个洞算是前面CVE-2020-1957补丁的一个绕过,前面学习CVE-2020-1957时了解到的修复是针对PathMatchingFilterChainResolver#getChain获取URI的底层实现不再直接request.getRequestURI()

而是拼接getContextPath()+getServletPath()+getPathInfo()

image-20220418224156118

在这里下断点,看看当我们请求;/bypass/;/bypass/;/aaa时各个部分的取值

request.getContextPath()得到/;/bypass

image-20220418224846581

request.getServletPath()得到/bypass/aaa

image-20220418224947024

request.getPathInfo()得到null

image-20220418225044911

则最终拼接得到/;/bypass//bypass/aaa,然后进入decodeAndCleanUriString()方法

image-20220418225109373

前面讲过这个decodeAndCleanUriString()方法遇到;号会截断

image-20220418225157988

所以会返回一个/

最终在getChain处,requestURI的值是/,用/去参与Ant匹配可以正常通过Shiro的鉴权。

image-20220418225413166

而spring处理/;/bypass//bypass/aaa请求可以正常顺利的到达路由。

所以这个越权的大锅在request.getContextPath()这里

URL编码

前置条件

  1. PathVariable 注解的参数只能是 String 类型
  2. Ant匹配/*

参考腾讯文章

前面讲到getRequestUri()方法取uri的值时,拼接getContextPath()+getServletPath()+getPathInfo()后会进入decodeAndCleanUriString()方法

image-20220418230105109

这里对uri做了一次url解码操作,也就是说Shiro会对url有2次解码,而tomcat只会进行1次解码,利用中间的差异性就能达到绕过鉴权的效果。

当Ant表达式这样写

map.put(“/login”, “anon”);
map.put(“/aaaaa/**”, “anon”);
map.put(“/bypass/*”, “authc”);

正常触发/bypass/xx 这个路由就是我们的目的

image-20220418231304280

请求bypass/bypass/2%252f1,在PathMatchingFilterChainResolver#getChain下断点

image-20220418231421001

可以看到getChain的requestURI就变成了/bypass/2/1

当我们Ant配置/bypass/*时,是匹配不到/bypass/2/1的,只有当Ant配置/bypass/**才能匹配到/bypass多级子目录的内容,所以可以绕过Ant的匹配实现权限绕过

实战

只有当配置/byass/*这种Ant表达式才能触发,而且两种利用方式都还有一些各自的额外条件。

所以总的来说感觉这个漏洞实战不太容易黑盒遇见。

一定要打就下面两种POC

1
2
/;/bypass
/bypass/a%2f33s

修复

https://github.com/apache/shiro/commit/01887f645f92d276bbaf7dc644ad28ed4e82ef02

首先不再用request.getContextPath()额外处理context

image-20220418232708902

还有getPathWithinApplication()方法之前是从requestUri减去contextPath,现在是getServletPath()加上getPathInfo(),然后用removeSemicolon方法截断;号,现在直接拼接得到uri,所以不会再触发decodeAndCleanUriString做url解码。

image-20220418232838482

CVE-2020-13933

https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2020-13933

影响范围:shiro < 1.6.0

原理

前置条件

  1. PathVariable 注解的参数只能是 String 类型
  2. Ant匹配/*

这个漏洞是CVE-2020-11989补丁的一个绕过。

CVE-2020-11989补丁后,Shiro通过拼接getServletPath()+getPathINfo(),然后执行removeSemicolon()方法处理;号,最后normalize()方法标准化路径。其中getXXX(request)方法会做一次url解码操作。

即Shiro获取URI,首先是url解码处理,其次才会对;号做处理。

然而在Spring的处理顺序却略有不同,在方法UrlPathHelper#decodeAndCleanUriString

image-20220419115848742

是先用removeSemicolonContent()方法对;号做处理,再做url解码处理。

而这个对;做处理的实现中,显然我们url编码可以绕过这里对;号的处理。

image-20220419120506562

举一个简单的例子说明Shiro和Spring对URI处理的差异,当我们发起/bypass/%3bok请求时,

  • Shiro会先对url做解码,然后遇到;号做截断处理,最终参与Ant匹配的是/bypass/,而用Ant配置/bypass/*匹配不了/bypass/,绕过了权限鉴定
  • Spring会先处理;号,但是我们url编码可以绕过这个处理,其次才会做url解码,则经过Spring处理得到/bypass/;ok,Spring把;ok整体当成字符串,参与Handler的匹配,可以匹配到/bypass/{id}这个路由

关于Spring的详细处理过程可以参考这个Hu3sky师傅的文章,总之由于Spring和Shiro处理后结果的差异,导致我们可以实现权限绕过

image-20220419121139072

实战

因为必须配置/bypass/*才能利用,一般程序员不太会这样写,所以实战中黑盒很难碰到..

POC记录

1
2
在要访问的路径前加%3b
例如/manager/%3bindex 越权访问/manager/*路由的功能

修复

Shiro 1.6.0对这个漏洞做了修复,参考https://github.com/apache/shiro/commit/dc194fc977ab6cfbf3c1ecb085e2bac5db14af6d

新增了InvalidRequestFilter用于全局匹配过滤

image-20220419141043673

isAccessAllowed方法遇到下面中任一个字符就会返回false

  • 号及其url编码
  • \号及其url编码
  • 非Ascii字符

CVE-2020-17510

https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2020-17510

影响范围:shiro < 1.7.0

原理

前置条件

类似CVE-2020-11989

感觉也算上面漏洞的绕过,还是利用Spring和Shiro处理uri字符的结果差异实现权限绕过,这次用到的是%2e

具体效果如su18师傅文章中所述:

例如访问:”/audit/%2e/“时

  • Shiro url decode:”/audit/./“
  • Shiro 标准化路径:”/audit/“
  • Spring 标准化路径:”/audit/%2e/“
  • Spring url decode:”/audit/.”

和上面一个漏洞很像,Shiro和Spring处理后得到的uri值不一样,Shiro那边/audit/通过了权限验证,然而Spring这边真正访问的却是/audit/.,达成了权限绕过。

但我这里复现不了su18师傅文章中的情景,我用的spring-boot-starter-parent-2.3.xspring-boot-starter-parent-1.5.x搭建的demo环境spring部分的代码和师傅的代码有出入。

我这边复现时遇到的关键点在UrlPathHelper.getLookupPathForRequest,这里会由于不同的spring版本导致return的结果不同。

image-20220419165526659

这里有个关于springboot %2b的trick,如Ruilin师傅所说:

当Spring Boot版本在小于等于2.3.0.RELEASE的情况下,alwaysUseFullPath为默认值false,这会使得其获取ServletPath,所以在路由匹配时相当于会进行路径标准化包括对%2e解码以及处理跨目录,这可能导致身份验证绕过。而反过来由于高版本将alwaysUseFullPath自动配置成了true从而开启全路径,又可能导致一些安全问题。

假如请求/bypass/%2e2e

在高版本的Spring中,由于alwaysUseFullPath默认为true,所以在UrlPathHelper#getLookupPathForRequest直接返回了用this.getPathWithinApplication()处理后的/bypass/..,所以在Spring中仍然把..当成字符拿去请求/bypass/*路由。

image-20220419161808887

直接绕过

image-20220419161532970

但是当Spring版本不高时,用getPathWithinServletMapping处理后得到的却是bypass/..,少了关键的一截/导致匹配不上,

看看getPathWithinServletMapping中的处理,核心代码如下

image-20220419172827061

关键在这个getRemainingPath方法

getRemainingPath把requstUri和mapping差异的部分取出来,即把getPathWithinApplicationgetServletPath结果中相同的部分做个减法。

image-20220419172613066

这个处理很恶心,直接导致我在spring-boot-starter 2.3.0及以下的版本我这边都复现不了。

不过我换上更高版本的Shiro后,在高版本spring依然可以利用%2e这个姿势绕过。

实战

实战中也有点鸡肋..

在要尝试越权的路由后面加上盲测POC

1
2
3
4
%2e
%2e/
%2e%2e
/%2e%2e/

修复

https://github.com/apache/shiro/commit/6acaaee9bb3a27927b599c37fabaeb7dd6109403

建议看看su18师傅的分析

创建了 UrlPathHelper 的子类 ShiroUrlPathHelper,并重写了 getPathWithinApplicationgetPathWithinServletMapping 两个方法,全部使用 Shiro 自己的逻辑 WebUtils#getPathWithinApplication 进行返回。

设置后,Spring 匹配 handler 时获取路径的逻辑就会使用 Shiro 提供的逻辑,保持了二者逻辑的一致。从而避免了绕过的情况。

CVE-2020-17523

https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2020-17523

影响范围: shiro < 1.7.1

原理

前置条件

类似CVE-2020-11989

又一个和前面差不多的绕过,这次换了个字符%20

请求/bypass/%20

PathMatchingFilterChainResolver#getChain断点看看Shiro中的处理

image-20220419180547547

会进入pathMatches方法匹配pattern

image-20220419175701910

一路可以跟到doMatch方法

image-20220419175927320

可以看到在tokenizeToStringArray对path参数处理后空格被处理掉了,一路跟tokenizeToStringArray方法

image-20220419180333433

可以看到有一个trim()处理空格,即Shiro在做pattern匹配时默认会处理空格。最终/bypass/%20变成了/bypass/,绕过Shiro鉴权。

而在Spring对uri的处理流程中,默认把%20当成字符处理了,所以可以正常匹配到/bypass/*这个路由的handler

image-20220419180721761

成功绕过

image-20220419174642420

参考这个思路,fuzz发现在Shiro 1.6.0以前%00--%20都可以绕过

image-20220419174712080

但是前面讲到CVE-2020-13933的补丁对非ASCII字符做了判断,所以在Shiro 1.7.0只有%20这个特殊字符。

实战

这一篇文章讲的洞,截至目前为止,利用场景都和CVE-2020-11989的场景类似,都有很多条件限制,实战很难遇到。

黑盒盲打情景就在想要尝试越权的路由后面加上%20

1
/admin/%20

修复

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

关键就是tokenizeToStringArray这里第三个参数设置了false

image-20220419181113100

这个参数为false则默认不会做trim()操作处理空格

image-20220419181205074

CVE-2021-41303

https://issues.apache.org/jira/browse/SHIRO-825

影响范围: shiro =1.7.1 ?

原理

搜了一下公开分析这个CVE的好像只有threedr3am师傅su18师傅,但两个师傅也没有百分百确定这个CVE就是它们分析的这样,因为这个如果算成一个漏洞的话确实有点鸡肋..不管了记录下师傅们的分析..

首先是Shiro-825的一个bug

当配置

map.put(“/audit/list”, “authc”);
map.put(“/audit/*”, “anon”);

/audit/目录下只有list需要权限验证,

此时我们访问/audit/hello正常

image-20220419213653928

但访问/audit/hello/抛出报错

image-20220419213919140

跟到DefaultFilterChainManager#proxy

image-20220419215458378

根据我们console里的报错信息,可以推断是this.getChain这里返回了null,跟进getChain方法

image-20220419215607691

这里的chainName竟然是/audit/hellothis.filterChains里当然是没有这个chainName的,所以会抛出报错

image-20220419215657930

根据报错的堆栈网上找找chainName是怎么来的,跟到DefaultFilterChainManager#proxy的上面PathMatchingFilterChainResolver#getChain

image-20220419220322966

这里的requestURINoTrailingSlash就是后面的chainName,而requestURINoTrailingSlash的值是removeTrailingSlash(requestURI)拿到的,这里removeTrailingSlash就是把reuqestURI去除/后缀,这个就是我们用户输入可控的,即Shiro会去自己的filterChains中找完全跟用户输入相匹配的uri,显然是找不到的。threedr3am师傅分析了似乎只有Shiro 1.7.1这里是这样实现的,所以这个bug只影响Shiro 1.7.1。

那怎么利用这个bug实现越权呢?首先配置

map.put(“/audit/*”, “auth”);
map.put(“/audit/list”, “anon”);

注意这个顺序,虽然/audit/list可以支持匿名访问,但是/audit/*在前面,所以正常情况下访问/audit/list也需要权限认证。

正常访问/audit/list提醒登录

image-20220419221548187

但是访问/audit/list/可以绕过

image-20220419221608463

实战

这个洞真是鸡肋他妈给鸡肋开门,鸡肋到家了..

我不太觉得实战环境能遇到这洞…要黑盒找就随缘加个/符号在末尾试试..

修复

https://github.com/apache/shiro/commit/4a20bf0e995909d8fda58f9c0485ea9eb2d43f0e

requestURINoTrailingSlash换回pathPattern即可

image-20220419221706317

总结

总算把su18师傅文章里梳理的Shiro权限绕过CVE全部过了一遍,有一说一,真正有用点的洞好像还是上一篇文章里的Shiro-682和CVE-2020-1957…目前为止看到的这些洞,可以用以下几点总结

  • Ant表达式的/bypass/*匹配不到/bypass/或/byapss/xxx/
  • Spring版本对CVE有影响,主要是alwaysUseFullPath默认值不同,建议阅读Ruilin师傅 Spring Boot 中关于 %2e 的 Trick
  • 框架例如Spring和Shiro对URI的处理顺序或对某些特殊字符处理有差异(CVE-2020-13933,CVE-2020-17510,CVE-2020-17523),在CVE-2020-17510补丁后Spring 匹配 handler 时获取路径的逻辑就会使用 Shiro 提供的逻辑,保持了二者逻辑的一致。
  • 更新的补丁存在缺陷(CVE-2020-11989,CVE-2021-41303)
  • 最强大的洞个人觉得是CVE-2020-1957,可以绕过/**的匹配
  • 还有个李三师傅用ajp协议实现越权,所以估计后续还会有一些不同协议对URI处理差异的细节可以利用。
Prev
2022-04-19 22:36:19
Next