freeBuf
主站

分类

漏洞 工具 极客 Web安全 系统安全 网络安全 无线安全 设备/客户端安全 数据安全 安全管理 企业安全 工控安全

特色

头条 人物志 活动 视频 观点 招聘 报告 资讯 区块链安全 标准与合规 容器安全 公开课

官方公众号企业安全新浪微博

FreeBuf.COM网络安全行业门户,每日发布专业的安全资讯、技术剖析。

FreeBuf+小程序

FreeBuf+小程序

Shiro(全系漏洞分析-截至20230331)
2023-03-31 22:36:38
所属地 广东省

概述

Apache Shiro是用来做认证和授权的框架,执行身份验证、授权、密码和会话管理。

Shiro主要配合一些容器的使用,如Tomcat、Weblogic等;同时有些框架也会将Shiro集成用来做身份认证和授权,比如:SpringBoot等;

Shiro包括几个重要的类:

  1. Subject:项目,表示需要受Shiro保护的项目;

  2. SecurityManager:安全管理器,管理所有Subject;

  3. Realm:域,用来访问数据,比如访问数据库中的用户密码以及权限角色等信息,使用该类来完成;

Shiro存在一些默认的过滤器

  • anon:为匿名过滤器

  • authc:为登录过滤器

学习shiro前要了解他的使用和原理,这样也比较好懂他的成因,下面推荐两个文章;

从Shiro诞生之初,到至今一共存在14个漏洞。以下是shiro官网中的漏洞报告:

CVE-2010-3863

漏洞描述

1.1.0 之前的 Apache Shiro 和 JSecurity 0.9.x 在将它们与 shiro.ini 文件中的项进行对比之前不会规范化 URI 路径,这允许远程攻击者通过精心设计的请求绕过预期的访问限制,如:/./account/index.jsp

适用范围

  • Shiro < 1.1.0

  • JSecurity 0.9.X

分析

远古版本了属于是,搭个环境给我整了半天。Shiro可以根据ini配置文件去读取用户名密码以及角色权限等信息用来做认证和授权;

[users]
zhang=123,admin
wang=123,admin,vip
# 每一行定义一个用户, 格式是 username = password, role1, role2, ..., roleN

[roles]
admin=user:delete
# 角色在这里定义, 格式是 roleName = perm1, perm2, ..., permN
# 说明1: 权限名可以使用带有层次的命名方式, 使用冒号来分割层次关系, 比如 user:create 或 user:poweruser:update 权限.
# 说明2: user:* 这样的权限, 代表具有 user:create 和 user:poweruser:update 权限.

[urls]
/static/**=anon
/login=anon
/authc/admin/user/delete=perms["user:delete"]
/authc/admin/user/create=perms["user:create"]
/authc/admin/**=roles[admin]
/authc/home=roles[admin,vip]
/authc/**=authc

在request请求到达web容器后,会调用PathMatchingFilterChainResolver的getChain方法获取FilterChain

1680269982_6426e29eef0665469925e.png!small?1680269982865

这个pathMatches方法,其实就是比对你的ini文件中的urls标签下的每一项跟请求的uri是否相等,如果相等,则使用Shiro代理的FilterChain,即ProxiedFilterChain

1680269988_6426e2a4f04e94bd1bae1.png!small?1680269988808

当执行完Shiro的ProxiedFilterChain后,就执行原来的FilterChain

导致该漏洞的原因就在于,PathMatchingFilterChainResolver的getChain方法中,在获取请求uri时未标准化路径

1680269995_6426e2abdf091d78da96b.png!small?1680269995992

它使用getPathWithinApplication获取请求URI,在getPathWithinApplication方法中继续调用getPathWithinApplication

1680270002_6426e2b2b8eadf1b30d78.png!small?1680270002472

然后在WebUtils的getPathWithinApplication中,调用getRequestUri方法

1680270009_6426e2b994a3e19334dc0.png!small?1680270009420

先从javax.servlet.include.request_uri取,取不到在调用getRequestURI获取请求uri,然后调用decodeAndCleanUriString

1680270016_6426e2c081c5b095fb173.png!small?1680270016302

在decodeAndCleanUriString中,59为分号,分割分号前的一部分并返回,这是因为一些中间件会在 url 处添加 ;jsessionid,所以这里对 ;进行了截取。

1680270023_6426e2c7a766298deee82.png!small?1680270023463

全流程看下来,他并没有对路径进行标准化处理,啥是标准化处理,就是去除路径中的/./\\以及其他一些特殊符号。

这就可以绕过权限认证了,当我配置了/index需要认证才能访问,此时我就可以用/./index绕过认证直接访问。

修复

在后续版本增加了标准化处理,在WebUtils的getPathWithinApplication方法中,调用了normalize进行标准化处理

1680270032_6426e2d022c3756ab1d7e.png!small?1680270032013

对路径中使用\的替换为/,如果路径为/.返回/,如果路径不以/开头,就给他加个/,然后当路径中有//,去除一个/,当路径中有/./,去掉/.,当路径中有/../,将直接移除/..

1680270038_6426e2d6648d7c7d64f97.png!small?1680270038418

CVE-2014-0074/Shiro-460

漏洞描述

1.2.3 之前的 Apache Shiro,当使用的 LDAP 服务器启用了无需身份验证即可绑定的功能时,允许远程攻击者通过空用户名或空密码绕过身份验证。

适用范围

  • Shiro < 1.2.3

分析

在skis提出的issue中,当在shiro.ini文件中指定了以下几项:

ldapContextFactory = org.apache.shiro.realm.ldap.JndiLdapContextFactory
ldapContextFactory.url = ldap://abc.internal:389/

adRealm = org.apache.shiro.realm.activedirectory.ActiveDirectoryRealm
adRealm.ldapContextFactory = $ldapContextFactory
adRealm.searchBase = "CN=Configuration,DC=abc,DC=internal"

指定从AD域中去获取用户的身份信息完成认证和授权

这个配置文件指定了JndiLdapContextFactory类,这个类的getLdapContext方法是用来获取LDAP上下文的;

getLdapContext方法的执行流程是,将验证信息封装到Hashtable中,然后使用该验证信息去连接LDAP服务器,获取LDAP上下文;

1680270047_6426e2df45e19e961fe12.png!small?1680270047169

而关键就在于当LDAP服务器允许匿名访问(Anonymous)时,可以使用空用户和空密码登录,同时当LDAP开启任何人bind时,可以使用空用户和任意密码登录。这本质上来说是LDAP服务器的问题,但Shiro还是给了CVE且进行了修复

1680270053_6426e2e520041de0db0d6.png!small?1680270053007

在新版本中新增了validateAuthenticationInfo方法,但是修了等于没修

1680270060_6426e2ec2220821d96a35.png!small?1680270059983

他只是判断了用户名不为空且有密码就抛出异常,跟以上两种情景毫无关联,所以说修了等于没修

CVE-2016-4437/Shiro-550

漏洞描述

在Shiro 1.2.5之前,当没有为“remember me”功能配置密钥时,允许远程攻击者执行任意代码或通过请求参数绕过预期的访问限制。

适用范围

  • Shiro < 1.2.5

分析

rememberMe功能由RememberMeManager提供,RememberMeManager是个接口,其实现类为

1680270067_6426e2f3a78152884a886.png!small?1680270067391

在AbstractRememberMeManager中,他将密钥直接写在了代码中

1680270072_6426e2f8b34d65741f37f.png!small?1680270072937

接下来跟一下调用流程

1680270077_6426e2fdec612d5b4bd59.png!small?1680270077811

在DefaultSecurityManager的createSubject方法中调用resolvePrincipals方法解析rememberMe参数,识别用户身份

1680270083_6426e303cbdc5cdfed5b3.png!small?1680270083658

先从context中解析用户身份,解析不到再调用getRememberedIdentity方法中解析用户身份

1680270222_6426e38ee3a140777853d.png!small?1680270222899

DefaultSecurityManager不具备解析rememberMe参数的能力,交由给RememberMeManager去解析,他会调用getRememberedPrincipals方法去解析rememberMe参数

1680270229_6426e395d68c44553e5e8.png!small?1680270229687

在AbstractRememberMeManager的getRememberedPrincipals方法中调用getRememberedSerializedIdentity获取用户标识

1680270236_6426e39c896a691615d04.png!small?1680270236403

getRememberedSerializedIdentity方法中将rememberMe参数的值进行base64解码,然后返回

1680270243_6426e3a311089f85525c2.png!small?1680270242898

返回的字节再调用convertBytesToPrincipals进行转换

1680270248_6426e3a8b5af3fe8a78ce.png!small?1680270248490

先AES解密,然后再反序列化,下面就没必要看了,就是常规的获取反序列化器进行反序列化,这里就造成了反序列化漏洞,可以配合CC链以及其他链去执行任意代码

整个流程梳理下来就是:

  1. 若存在RememberMe属性,则获取他的值

  2. 将值进行base64解码

  3. 然后再进行AES解密

  4. 然后进行反序列化

所以只要我们控制了AES密钥,同时服务器存在利用链就可以进行反序列化攻击,执行任意代码

修复

如一开始看到那样,在Shiro 1.2.5之前都是使用了默认密钥,而在1.2.5及后续的版本,都是随机产生的密钥

1680270256_6426e3b02a7e1657babe4.png!small?1680270255980

CVE-2016-6802

漏洞描述

1.3.2 之前的 Apache Shiro 允许攻击者绕过预期的 servlet 过滤器并通过利用contextPath获得访问权限。

适用范围

  • Shiro < 1.3.2

分析

跟CVE-2010-3863有点相似,在PathMatchingFilterChainResolver的getChain方法中,获取requestURI时调用getPathWithinApplication方法

1680270263_6426e3b78ce75a78cc735.png!small?1680270263776

而在getContextPath方法中未标准化路径

1680270269_6426e3bd0ca1cb7889bab.png!small?1680270269339

进入getContextPath方法查看,他获取contextPath后,直接URL解码并返回,未作标准化路径的操作

1680270276_6426e3c4b962ac3954d4f.png!small?1680270277873

返回到getPathWithinApplication方法中,获取完contextPath之后直接截取

1680270285_6426e3cd36c8be8e15fa9.png!small?1680270285002

若contextPath为/shiro,当传递的uri为/aaa/../shiro/abc,此时getContextPath获取的contextPath为/aaa/../shiro,getRequestUri获取的requestUri为/shiro/abc,因为/shiro/abc不以/aaa/../shiro开头,所以直接返回/shiro/abc

而为什么getContextPath返回的contextPath不是我们设置的/shiro呢?这是因为在Tomcat的Request的getContextPath方法对/aaa/../shiro/abc进行截取最后一个/之前的部分,得到得/aaa/../shiro,然后标准化后得到/shiro,比对我们设置的/shiro,若相等,就返回/aaa/../shiro

1680270298_6426e3da4b370f597503e.png!small?1680270298327

正常情况下,我们配置需要登录才能访问的路径都不会带上contextPath

当设置需要登录才能访问的路径为/abc,而/shiro/abc成功的进行了绕过

1680270327_6426e3f7691c63af135ea.png!small?1680270327311

由于匹配不到在Shiro中设置的路径,从而绕过了Shiro过滤器,没有Shiro过滤器的把持下,就不需要进行认证和授权

后续会继续分离/shiro/abc,进行/abc路由的访问

修复

在1.3.2及之后的版本中,在WebUtils的getContextPath中,在返回前进行了标准化处理

1680270346_6426e40aedee2884134ff.png!small?1680270346794

CVE-2019-12422/Shiro-721

漏洞描述

在1.4.2之前的Apache Shiro,当使用默认的 "rememberMe "配置时,cookies可能容易受到填充攻击。

适用范围

  • Shiro < 1.4.2

分析

跟Shiro无关,而是对Shiro采用的加密方式进行的攻击,所以略过,只要了解了Padding Oracle Attack 原理就能理解这个攻击的原理,这里推荐Padding Oracle Attack(填充提示攻击)详解及验证

CVE-2020-1957/Shiro-682

漏洞描述

1.5.2之前的Apache Shiro,当使用Apache Shiro与Spring动态控制器时,特制的请求可能导致认证绕过。

适用范围

  • Shiro < 1.5.2

分析

根据提出的issues得知,当访问/index会被限制,而在后面加上/,则可以绕过访问限制。

这是由于Spring-Web和Shiro的访问路径的不同处理造成的,在Spring-Web中,/resource/menus/resource/menus/都可以访问资源,而在Shiro中,只有/resource/menus才会匹配上pathPattern从而进行身份验证和授权之类的操作。

当访问/index/时,请求先进入Shiro的filter中,在PathMatchingFilterChainResolver进行路径匹配

1680270378_6426e42ad3832c459613e.png!small?1680270378761

由于设置需要身份验证的路径为/index,这里匹配不到返回null,然后进入Spring-Web的DispatcherServlet中,在DispatcherServlet的doDispatch方法中获取Handler

1680270384_6426e43078ed13a35282b.png!small?1680270384492

调用getHandler获取handler,后续在PathPattern#match方法中对/admin/list//admin/list的匹配都会返回 true。

除此之外,还通报了另一个绕过,利用的是Shiro和Spring-Web对url中的;处理的差别来绕过校验。

Shiro配置中将/abc/admin设置成了需要认证才能访问

POC:/abc;bbb/admin

在WebUtils的decodeAndCleanUriString方法中,他会截取分号之前的内容返回,即/abc

1680272791_6426ed97aac8dad89034b.png!small?1680272791834

然后会因为匹配不到Shiro的pathMatches从而绕过Shiro的filter

然后进入Spring-Web的处理

1680272797_6426ed9d3d6370ad28568.png!small?1680272797215

最后返回的uri为/abc/admin,从而成功访问到加了限制的路由

修复

在后续的版本中,在PathMatchingFilterChainResolver的getChain方法中,若路径最后一个符号为/号,则去掉

1680272802_6426eda29f1a7b3b8f657.png!small?1680272802442

同时为了统一,若在配置文件中指定需要认证的路径的最后一个字符/,也直接去掉

1680272807_6426eda7afddc792a7207.png!small?1680272807724

而对于;的绕过,修复后直接使用contextPath、servletPath和pathInfo进行拼接得到,这些都是通过中间件的调用得到的

1680272813_6426edad0925fa844fda7.png!small?1680272812839

直接获取中间件处理好的结果进行拼接,而不是自己去处理,但是有个坑点就是,他拼接完之后又会调用decodeAndCleanUriString方法去截取;之前的部分再标准化路径返回。

但是对于这个POC/abc;bbb/admin来说,再用decodeAndCleanUriString方法之前他已经拼接好了路径为/abc/admin,就不用去截取了,但是若从中间件获取到的路径带有分号的话还会去截取分号之前的部分,造成跟Spring-Web处理不相同而导致的漏洞

CVE-2020-11989/Shiro-782

漏洞描述

1.5.3之前的Apache Shiro,当使用Apache Shiro与Spring动态控制器时,一个特制的请求可能会导致认证绕过。

适用范围

  • Shiro < 1.5.3

分析

第一个绕过

当Shiro使用Ant风格的路径表达式配置路径时,就有可能导致权限绕过

通配符说明
?匹配任何单字符
*匹配0或者任意数量的字符
**匹配0或者更多的目录

例如:/admin/*可以匹配/admin/xxx,但是不能跨目录,而/admin/**可以匹配/admin/xxx也可以匹配/admin/xxx/xxx,可以跨目录

当配置/admin/*需要认证才能访问,而访问的uri为/admin/aa/bb能进行绕过,因为/admin/*无法跨目录

此次漏洞的原因在于getPathInfo获取路径时会进行解码

1680272821_6426edb597e687d215b4d.png!small?1680272822824

解码完再调用decodeAndCleanUriString又会进行一次解码

而进入Spring-Web的处理时,只会将uri解码一次,这就造成了此次漏洞

将斜杆两次编码

/ -> %2f ->%25%32%66

此时配置/admin/*需要认证才能访问,我们访问/admin/a%25%32%66b会被Shiro解码成/admin/a/b,此时就绕过了匹配,当进入Spring容器时,Spring容器只会进行一次解码,将/admin/a%25%32%66b解码成/admin/a%2fb,这时候就成功的进行了访问

但是正常人谁会把访问的路由写成/a%2fb,结果还真有这种情景,就是当把路径作为参数时,

也就是如下情况

@GetMapping(value="/admin/{name}")
@ResponseBody
public void list(@PathVariable String name){
 xxx
}

动态接收name的值作为请求参数进行处理

第二个绕过

还有一个坑点,是利用;进行的绕过,也就是1957未修复的坑点

1680272828_6426edbc720fae96da730.png!small?1680272828268

也就是在获取contextPath时,会有带有分号的情况,然后调用decodeAndCleanUriString会截取分号之前的部分,举个例子:

我们配置/xx需要认证才能访问,我们访问的uri为/;/admin/xx,则通过request的getContextPath方法获取到的contextPath为/;/admin,经过decodeAndCleanUriString方法的洗礼,他会截取;之前的部分,即/,然后使用/去匹配需要认证的路由,未匹配上,成功的进行了绕过;而后续在Spring-Web的处理中,他会处理成/admin/xx,这时候就成功访问到了/admin/xx的controller且无需认证

原理是在调用Tomcat的Request的getContextPath方法获取contextPath时

1680272836_6426edc4138011b8ac176.png!small?1680272836102

1680272842_6426edca632e73c368db3.png!small?1680272842549

修复

在后续的版本中,去除getRequestUri的调用,那么就同时修复了两个绕过

1680272850_6426edd2e7dc3479f6f2c.png!small?1680272850813

1680272855_6426edd704fc03ab95bd8.png!small?1680272854922

CVE-2020-13933

漏洞描述

1.6.0之前的Apache Shiro,当使用Apache Shiro时,特制的HTTP请求可能导致认证绕过。

适用范围

  • Shiro < 1.6.0

分析

还是利用SpringWeb和Shiro处理方式的差异进行绕过

在Shiro中,先解码、后处理分号,然后再标准化路径

1680272860_6426eddcc12e3ed62cc7e.png!small?1680272860745

在SpringWeb中,先处理分号,再解码,然后标准化路径

1680272865_6426ede115c4c01e1b31c.png!small?1680272864953

主要是前面两个步骤的差距,可以导致绕过

我们将;编码成%3b

  1. 在Shiro中,他会先解码成分号然后截取分号前的内容

  2. 在SpringWeb中,首先他找不到分号,因为分号已经被编码成%3b,相当于他不会处理分号,然后他才将分号解码

所以我们可以使用;去绕过一些需要认证的路径,只要他截取的分号前的内容设置了不需要验证即可

/admin/*需要认证才能访问,而我们通过加个%3b,即/admin/%3bxx,在Shiro中,他会解码成分号,然后截取分号前的内容,即/admin/,这里就绕过了Shiro的过滤器,在后续Spring-Web处理中,该路径被处理成/admin/;xx就成功访问到了controller

同样,也是如下场景

@GetMapping(value="/admin/{name}")
@ResponseBody
public void list(@PathVariable String name){
 xxx
}

修复

Shiro新增了个全局过滤器,为InvalidRequestFilter

1680272877_6426ededa0d3bdc38b61a.png!small?1680272877500

这个类InvalidRequestFilter会检测uri是否存在分号、反斜杠以及非ASCII码字符,若检测到就会返回状态码400,消息为Invalid request

CVE-2020-17510

漏洞描述

1.7.0之前的Apache Shiro,当与Spring一起使用Apache Shiro时,特制的HTTP请求可能导致认证绕过。

适用范围

  • Shiro < 1.7.0

分析

同样还是SpringWeb和Shiro的处理差异造成的

这次是因为.号造成的绕过,.号编码为%2e

那么对于Shiro和SpringWeb对待编码后的点号是怎么处理的呢?

在Shiro中,它会先解码成.,不需要处理分号,然后标准化路径去掉.

1680272884_6426edf46a02fcc9bc33c.png!small?1680272884204

而在Spring-Web中,首先无需处理分号,然后将%2e解码成.号,在getSanitizedPath也未处理.

1680272888_6426edf8479c0b2ee5765.png!small?1680272888205

所以可以使用编码后的点号进行绕过,同样也是设置/admin/*需要认证才能访问,此时我们加上%2e,变成/admin/%2e

在Shiro中,处理成/admin/,无法匹配/admin/*而造成绕过

在Spring中,处理成/admin/.,成功访问到了/admin下的路由

同样也是以下场景会造成该漏洞

@GetMapping(value="/admin/{name}")
@ResponseBodypublic
void list(@PathVariable String name)
{    
 xxx
}

修复

Shiro使用ShiroUrlPathHelper去继承了UrlPathHelper,然后将自己的ShiroUrlPathHelper注入到Spring中,替换掉默认处理的UrlPathHelper

1680272959_6426ee3f12d5a6c8a9a6c.png!small?1680272958934

1680272964_6426ee44e7bb29feb331c.png!small?1680272964815

这样使得Spring使用了Shiro的方式去处理路径,统一了处理方式

CVE-2020-17523

漏洞描述

1.7.1之前的Apache Shiro,当将Apache Shiro与Spring一起使用时,特制的HTTP请求可能导致认证绕过。

适用范围

  • Shiro < 1.7.1

分析

再次绕过,这次是通过空格编码进行的绕过,空格编码为%20

获取请求uri

1680272971_6426ee4bdd63a24ef76e0.png!small?1680272971902

这里只会解码%20,变成空格

1680272977_6426ee518b518d00ef94c.png!small?1680272977341

然后处理完,进行路径的匹配,进入pathMatches查看

1680272982_6426ee568ff9d167df870.png!small?1680272982716

进入

1680272987_6426ee5bab7bd3dbec97c.png!small?1680272987685

后面会来到AntPathMatcher#doMatch进行匹配

1680272994_6426ee62236ae29e48234.png!small?1680272994240

进入tokenizeToStringArray

1680272999_6426ee67aec17e0d2ed3f.png!small?1680272999497

进入tokenizeToStringArray

1680273006_6426ee6e9c20e5ff88f29.png!small?1680273006543

其实就是使用/分割字符串然后去除空格的这么一个操作,其实总的来说就是去掉空格

而SpringWeb跟Shiro也是一样的处理

设置/admin/*需要认证才能访问,我们使用/admin/%20,经过Shiro他会处理成/admin/,匹配不上/admin/*从而导致绕过,同样也是以下场景会导致该漏洞

@GetMapping(value="/admin/{name}")
@ResponseBodypublic
void list(@PathVariable String name)
{    
 xxx
}

修复

StringUtils#tokenizeToStringArray方法的第三个参数 trimTokens 为 false,也就是不去除空格

1680273014_6426ee76383d1f4131cdc.png!small?1680273014345

1680273018_6426ee7a8ee8ee6ab4148.png!small?1680273018408

CVE-2021-41303/Shiro-825

漏洞描述

1.8.0之前的Apache Shiro,当与Spring Boot一起使用Apache Shiro时,特制的HTTP请求可能会导致认证绕过。

适用范围

  • Shiro < 1.8.0

分析

在上面那个CVE-2020-17523中,后续的1.7.1版本又增加了新的逻辑

image-20230330175140741

如图,它首先会比对原始uri,没比对到再去掉末尾斜杆再进行比对,比对到了就将去掉末尾斜杆uri进行传入

当设置如下配置时,就会产生漏洞

/admin/* authc
/admin/list anon

按理来说,当我们访问/admin目录下的任何路由(不跨目录)都需要鉴权,所以一般来说没有认证都是访问不了这个/admin/list

但是这个漏洞就是可以绕过第一个配置,去访问第二个路由

首先我们传入的poc为/admin/list/,依据顺序,他首先会匹配第一个,因为原始的uri:/admin/list/匹配不到/admin/*,此时会去掉uri的最后一个斜杆进行匹配,即/admin/list,此时就匹配到了/admin/*,然后进入filterChainManager.proxy,并将去掉斜杆的uri传入,即/admin/list

1680273025_6426ee81ec08f2b42a514.png!small?1680273026100

进入filterChainManager.proxy进行查看

1680273042_6426ee927e0b7290b664f.png!small?1680273042461

再进入getChain查看

1680273046_6426ee96e6a3d52632b31.png!small?1680273046863

从map中取,发现刚好有,而对于没有配置的,例如访问/admin/li/,他取不到,就会抛出异常

1680273050_6426ee9ae3ac004e4f9e0.png!small?1680273050858

回到对/admin/list/的访问中,在后续他会将代理filterChain返回

1680273057_6426eea14c1eb980c402d.png!small?1680273057341

代理filterChain有两个filter,第一个为登录过滤器,第二为匿名过滤器

1680273061_6426eea57de72ea8db0c6.png!small?1680273061255

匿名过滤器直接放行,此时就会去访问/admin/list的controller了,此时就成功的进行了绕过

1680273066_6426eeaa56b43e2f6ebd8.png!small?1680273066679

这个利用条件特别极端,正常人不会这样写配置

/admin/* authc
/admin/list anon

修复

在1.8.0中,传入的参数由requestURINoTrailingSlash变成pathPattern,即/admin/*而不是/admin/list

1680273072_6426eeb0d054769c90ad9.png!small?1680273072887

而在map中取到的就是需要认证的/admin/*

1680273077_6426eeb5e25cb4c89a7d6.png!small?1680273077848

这时就无法绕过第一个配置项去访问第二个配置项了

CVE-2022-32532

漏洞描述

Apache Shiro在1.9.1之前,RegexRequestMatcher可能被错误配置,导致在一些servlet容器上被绕过。使用RegExPatternMatcher的正则表达式中含有.的应用程序可能会受到权限绕过的影响。

适用范围

  • Shiro < 1.9.1

分析

1680273084_6426eebc64ef1b5a99c3d.png!small?1680273084086

Shiro有两种表达式可以匹配路径,一种是AntPathMatcher,另一种是RegExPatternMatcher,前面一直都是使用Ant风格,也就是第一种

通配符说明
?匹配任何单字符
*匹配0或者任意数量的字符
**匹配0或者更多的目录

第二种其实就是正则表达式

1680273091_6426eec31623edc62d4c4.png!small?1680273090994

这个漏洞很好理解,当配置了需要认证的路径为/admin/.*,而在正则表达式中元字符.是匹配除换行符(\n\r)之外的任何单个字符,\r的URL编码为%0d\n的URL编码为%0a,所以可以使用/admin/%0d或者/admin/%0a,这样就无法匹配/admin/.*从而绕过认证

修复

新增caseInsensitive,并且默认为false

1680273096_6426eec86485ae1b5aed1.png!small?1680273096299

Pattern.compile第二个参数为32,表示启用DOTALL模式,此时元字符.会匹配换行符(\n\r)在内的任何单个字符

CVE-2022-40664

漏洞描述

1.10.0之前的Apache Shiro,Shiro在通过RequestDispatcher进行请求转发(forward)或请求包括(include)时存在认证绕过漏洞。

适用范围

  • Shiro < 1.10.0

分析

在进行请求转发或包含时不会进行鉴权,导致绕过

首先添加如下配置:

map.put("/forward","anon");
map.put("/admin/list","authc");

设置/admin/list需要认证,/forward不需要认证

如下代码:

@GetMapping("/admin/list")
@ResponseBody
public String list(){
 return "访问成功!";
}

@GetMapping("/forward")
public String forward(){
return "forward:/admin/list";
}

直接访问/admin/list1680273120_6426eee03923beccce04a.png!small?1680273119945

1680273105_6426eed1d6cdda725fd16.png!small?1680273106115


会跳转到登录页面

而访问/forward

1680273127_6426eee7b5f29e5e20d76.png!small?1680273127432

访问成功,绕过/admin/list的鉴权

在Tomcat应用的整个过滤链中,他会调用doFilter去依次执行过滤链中的每个过滤器

1680273132_6426eeec154da9500d2dd.png!small?1680273132065

而doFilter又会调用internalDoFilter方法

1680273136_6426eef0687854848ee4b.png!small?1680273136272

在internalDoFilter中会去调用各个filter的doFilter方法

而在Shiro的doFilter方法中,若在一次请求中已经被Shiro的过滤器过滤过了的话,则不会再调用Shiro的filter再过滤一遍,然后将请求传给其他过滤链中的过滤器

1680273141_6426eef58ea95f51555b2.png!small?1680273141656

因为请求/forward的时候,他会走一遍Shiro的过滤器,然后将alreadyFilteredAttributeName设置为shiroFilter.FILTERED,代表已经过滤过

1680273146_6426eefab2c13d24a53db.png!small?1680273147124

然后在/forward的controller中,将请求进行转发

1680273151_6426eeff0414c1d3f7f49.png!small?1680273150729

而访问/admin/list时,这两次的路由访问相当于一次请求,因为前面访问/forward路由时已经经过Shiro的过滤器过滤了一遍,所以此时访问/admin/list路由,则不会走Shiro的过滤器,从而绕过认证

1680273155_6426ef035a4da4e300796.png!small?1680273155514

修复

而在Shiro的1.10.0及以后,出现了个ShiroFilterConfiguration类

1680273161_6426ef096d95f4c52c62e.png!small?1680273161172

这个属性filterOncePerRequest若为false,则代表每次调用都会执行过滤器,这个属性若为true,则代表每次请求都会执行过滤器

而在OncePerRequestFilter的doFilter方法中,新增了个判断

1680273165_6426ef0dec8c0ed20a9ab.png!small?1680273165959

CVE-2023-22602

漏洞描述

当把1.11.0之前的Apache Shiro与Spring Boot 2.6+ 一起使用时,特制的HTTP请求可能导致认证绕过。当Shiro和Spring Boot使用不同的模式匹配技术时,认证绕过就会发生。Shiro和Spring Boot < 2.6时都默认为Ant风格的模式匹配。

适用范围

  • SpringBoot > 2.6 且 Shiro < 1.11.0

分析

SpringBoot 2.6+后,Controller的路径匹配默认使用path_pattern_parser

2.6以前在配置文件中可以这样配置以使用path_pattern_parser

spring:
mvc:
pathmatch:
  matching-strategy: path_pattern_parser

2.6之前使用ant_path_matcher解析Controller的路径,Ant风格估计都很熟,前面说过好几次了,就当作复习一下

通配符说明
?匹配任何单字符
*匹配0或者任意数量的字符
**匹配0或者更多的目录

他们之间的差别在于:

ant_path_matcher的通配符可以在中间,如:abc/**/xyz

path_pattern_parser的通配符只能定义在尾部,如:abc/xyz/**,且可以使用{*path}接收多级路由。path可以随意取名,与@PathVariable名称对应即可。

首先把环境搭一下

1680273174_6426ef16a98f87ea88211.png!small?1680273174803

1680273178_6426ef1ace7ceb215a034.png!small?1680273178600

设置/login/**无需认证,/admin/**需要认证

1680273184_6426ef2021b5e35d583e7.png!small?1680273183891

再添加三个路由

1680273188_6426ef24cfa76093f4fc6.png!small?1680273188596

先正常访问/admin/ak,由于需要认证会跳到登录页面

1680273193_6426ef2932dd72a5a3e0e.png!small?1680273192917

这时访问/admin/..注意!!!!不能直接在浏览器地址栏这样输入然后回车,这里有个坑点,浏览器看到uri有..会自动将uri变成/,这个坑点导致我复现半天

1680273202_6426ef32419ff1617ad19.png!small?1680273201991

抓包,却变成了/

1680273207_6426ef373a1fa3c16b382.png!small?1680273207304

这时候改成/admin/..就行了

1680273211_6426ef3b3946fca55aaf0.png!small?1680273211175

这时候就访问成功了

1680273215_6426ef3fbac5209c1c6cf.png!small?1680273215439

在PathMatchingFilterChainResolver的getChain方法下个断点

1680273219_6426ef43a458437207fc0.png!small?1680273219655

之前说过,调用getPathWithinApplication方法后续会将路径标准化,经过normalize的处理,会将/admin/..变成/

1680273223_6426ef4772e3365d08cae.png!small?1680273223335

这时候由于pathMatches无法匹配,而返回null(暂且这么理解),绕过了Shiro的认证

1680273227_6426ef4be07658e63bef8.png!small?1680273227951

后续进入Spring的处理中,在ServletRequestPathUtils的parseAndCache方法中会获取请求uri,并设置PATH_ATTRIBUTE属性,进入ServletRequestPath的parse方法查看

1680273232_6426ef502b7a87839a08b.png!small?1680273232101

获取请求uri

1680273236_6426ef5460a19fe229389.png!small?1680273236263

获取到的为/admin/..,这里其实就是调用中间件的getRequestURI,后面那个if不重要,滤过,RequestPath.parse其实就是将uri包装了一下,包装成DefaultRequestPath类

1680273241_6426ef59080abedbf5e7c.png!small?1680273240838

然后设置属性PATH_ATTRIBUTE,并返回

1680273245_6426ef5d4cfebbf8ee776.png!small?1680273245062

这个属性很重要,然后就是Spring根据路径去匹配controller了

1680273250_6426ef6256ee4e88bca0a.png!small?1680273250153

这里的usesPathPatterns方法就是判断是否用了path_pattern_parser模式,为true,然后removeAttribute方法移除UrlPathHelper.PATH_ATTRIBUTE属性(这跟刚刚设置的PATH_ATTRIBUTE属性不同),然后进入ServletRequestPathUtils.getParsedRequestPath(request);

1680273256_6426ef680b237c95a0c7d.png!small?1680273255933

在getParsedRequestPath方法里,其实就是获取之前设置的PATH_ATTRIBUTE属性的值,也就是那个包装了uri:/admin/..的类

1680273261_6426ef6d2261989a05076.png!small?1680273261031

回到initLookupPath方法,requestPath.pathWithinApplication().value()这一连串操作其实就是获取uri,这个removeSemicolonContent就是为了移除分号

1680273277_6426ef7dca08061159b25.png!small?1680273277831

后续在AbstractHandlerMethodMapping的addMatchingMappings方法中寻找/admin/..匹配的controller,/admin/**匹配到了,放入List集合matches中

1680273282_6426ef82c718b916b174c.png!small?1680273282584

1680273287_6426ef8789cf2d0a0ef93.png!small?1680273287416

然后根据/admin/**路径去寻找Bean

1680273292_6426ef8c94b6649d1ba0f.png!small?1680273292372

1680273296_6426ef90ee0d45244aae9.png!small?1680273296674

然后就是执行这个Bean

1680273302_6426ef960f81c850affe7.png!small?1680273302154

1680273305_6426ef9950e95dcd5447f.png!small?1680273305010

其实就是利用/admin/..会被Shiro标准化为/的特性,绕过Shiro的路径匹配,然后由于controller设置了/admin/**从而匹配到了该路径并执行

修复

设置ant匹配路径

在application.yml设置使用ant匹配路径

spring:
mvc:
pathmatch:
  matching-strategy: ant_path_matcher

在AbstractHandlerMapping的initLookupPath方法中就会走else

1680273310_6426ef9e9bbe49c40c906.png!small?1680273310622

后续就会走到Shiro的获取uri的getPathWithinApplication方法,获取到的uri为/

1680273314_6426efa2808f42ef78b78.png!small?1680273314354

升级版本

将Shiro升级到1.11.0以上

1680273318_6426efa6a831e84c3ca39.png!small?1680273318755

继承了EnvironmentPostProcessor

该类的作用是在SpringBoot项目启动之前自定义环境变量,可以在项目启动之前从非标准springboot配置文件中读取相关的配置并填充到springboot上下文中。

总结

  • CVE-2010-3863因为getRequestUri未标准化路径,可以使用/./进行绕过

  • CVE-2014-0074/Shiro-460由于LDAP服务器开启匿名登录或未授权登录,导致可以使用空用户名搭配空密码或空用户名搭配任意密码

  • CVE-2016-4437/Shiro-550就是著名的反序列化漏洞了

  • CVE-2016-6802因为getContextPath未标准化路径,可以使用/../进行绕过

  • CVE-2019-12422/Shiro-721是对rememberMe功能加密方式的攻击

  • CVE-2020-1957/Shiro-682是由于SpringWeb和Shiro处理方式不同,导致可以使用加路径末尾加斜杆或路径中加分号进行绕过

  • CVE-2020-11989/Shiro-782是由于SpringWeb和Shiro处理方式不同,导致可以使用斜杆双编码或加分号进行绕过

  • CVE-2020-13933是由于SpringWeb和Shiro处理方式不同,导致可以使用分号编码进行绕过

  • CVE-2020-17510是由于SpringWeb和Shiro处理方式不同,导致可以使用点号编码进行绕过

  • CVE-2020-17523是由于SpringWeb和Shiro处理方式不同,导致可以使用空格编码进行绕过

  • CVE-2021-41303/Shiro-825是由于新增逻辑的错误,导致可以绕过第一个需要认证的路径而访问第二个匿名路径

  • CVE-2022-32532是由于Shiro正则代表路径,且路径中含有.号,导致可以使用%0d%0a进行绕过

  • CVE-2022-40664是由于Shiro在进行请求转发或包含时未进行鉴权导致绕过

  • CVE-2023-22602是由于SpringWeb和Shiro使用的路径处理模式不同,导致可以使用/..进行绕过

Reference

# 漏洞分析 # Java代码审计
本文为 独立观点,未经允许不得转载,授权请联系FreeBuf客服小蜜蜂,微信:freebee2022
被以下专辑收录,发现更多精彩内容
+ 收入我的专辑
+ 加入我的收藏
相关推荐
  • 0 文章数
  • 0 关注者
文章目录