作者:Seaer@深信服千里目安全实验室
原文链接:https://mp.weixin.qq.com/s/j_gx9C_xL1LyrnuFFPFsfg
Apache Shiro是一个功能强大且易于使用的Java安全框架,功能包括身份验证,授权,加密和会话管理。使用Shiro的API,可以轻松地快速地保护任何应用程序,范围包括小型的移动应用程序到大型的Web和企业应用程序。以下是Shiro的结构图。
Shiro提供了应用安全API(被Shiro框架开发团队成为安全四大基石的Authentication(认证), Authorization(授权), Session Management(会话管理), Cryptography(加密))
在概念层,Shiro 架构包含三个主要的理念:Subject,SecurityManager和 Realm。
漏洞名称 | 漏洞ID | 影响版本 | CVSS |
---|---|---|---|
Apache Shiro 1.2.4反序列化远程代码执行漏洞 | CVE-2016-4437/SHIRO-550 | Apache Shiro <= 1.2.4 | 8.1 |
Apache Shiro Padding Oracle Attack 远程代码执行漏洞 | CVE-2019-12422/SHIRO-721 | Apache Shiro < 1.4.2 | 7.5 |
Apache Shiro 身份验证绕过漏洞 | CVE-2020-1957/SHIRO-682 | Apache Shiro < 1.5.2 | 9.8 |
Apache Shiro 身份验证绕过漏洞 | CVE-2020-11989/SHIRO-782 | Apache Shiro < 1.5.3 | 9.8 |
Apache Shiro 身份验证绕过漏洞 | CVE-2020-13933 | Apache Shiro < 1.6.0 | 7.5 |
Apache Shiro 身份验证绕过漏洞(AJP协议绕过) | SHIRO-760 | 基于Tomcat版本 | 无 |
Apache Shiro < 1.2.3 身份验证绕过漏洞 | CVE-2014-0074/SHIRO-460 | Apache Shiro < 1.2.3 | 7.5 |
Shiro组件漏洞主要分为两种类型,一种是java反序列化造成的远程代码执行漏洞,另一种是身份验证绕过漏洞。在官方对rememberMe字段反序列化进行秘钥随机化,AES加密模式更换后,暂无新型反序列化漏洞的出现。但是在身份验证绕过的补丁修复上,多次出现绕过情况,多是由于补丁修复过程中考虑不周全。Shiro在今年上半年爆出了三个较为严重的身份验证绕过的漏洞,且影响版本较新,用户需要重点关注。
根据Shiro组件的漏洞,结合敏感配置的梳理,得到如下风险梳理的思维导图。
基于风险梳理思维导图,总结出2种可行的漏洞利用链。
Apache Shiro 1.2.4反序列化远程代码执行漏洞单独使用,即可完成GetShell。此漏洞利用前提是获取AES加密秘钥。一般情况,Shiro组件默认没有被更换,可以直接利用此漏洞。如果出现秘钥被更换,此漏洞需要结合任意文件读取或者任意文件下载,先获取到AES加密秘钥,再进行漏洞利用。
Apache Shiro Padding Oracle Attack 远程代码执行漏洞单独使用,即可完成GetShell。此漏洞利用前提需要获得一个正确的用户Cookie值,利用此漏洞进行GetShell至少要获取一个普通用户权限。
从高危漏洞列表中,针对部分近年高可利用漏洞进行漏洞深入分析。
Shiro组件漏洞分为两个种类:java反序列化漏洞、身份验证绕过漏洞。本章节重点进行两个类别的漏洞分析。
技术背景
Java反序列化漏洞原理:Java 提供了一种对象序列化的机制,该机制中,一个对象可以被表示为一个字节序列,该字节序列包括该对象的数据、有关对象的类型的信息和存储在对象中数据的类型。Java反序列化则是从一个源输入流中读取字节序列,再把它们反序列化为一个对象,并将其返回。Java反序列化漏洞的成因是攻击者通过序列化函数将自己精心构造的恶意对象序列化,将序列化数据发送到目标服务器的反序列化接口,当服务器在没有对序列化数据进行有效的安全验证,直接对序列化数据进行反序列化处理,执行恶意对象中的代码,造成攻击。
AES加密算法:使用Rijndael分组密码算法,属于对称加密。
AES加密算法涉及4种操作:字节替代(SubBytes)、行移位(ShiftRows)、列混淆(MixColumns)和轮密钥加(AddRoundKey)。
总体加密流程:
字节替代(SubBytes)
字节代替的主要功能是通过S盒完成一个字节到另外一个字节的映射。
解密使用S盒的逆进行还原。
行移位(ShiftRows)
行移位是一个4x4的矩阵内部字节之间的置换,用于提供算法的扩散性。
正向行移位:正向行移位用于加密,其原理图如下。其中:第一行保持不变,第二行循环左移8比特,第三行循环左移16比特,第四行循环左移24比特。
解密使用逆向行移位:第一行保持不变,第二行循环右移8比特,第三行循环右移16比特,第四行循环右移24比特。
列混淆(MixColumns)
将行移位得到的新的4x4矩阵,与另一个矩阵进行左乘运算,更改每一列的数值。
解密时,左乘与之前选取的中间矩阵的互逆矩阵,即可还原。
AES CBC模式
1 漏洞信息
1.1 漏洞简介
1.2 漏洞概述
Apache Shiro 1.2.5之前的版本在org.apache.shiro.mgt.AbstractRememberMeManager
中存在AES默认秘钥kPH+bIxk5D2deZiIxcaaaA==
,开启RememberMe功能的shiro组件将允许远程攻击者构造序列化数据,在目标服务器上执行任意命令。
1.3 漏洞利用条件
1.4 漏洞影响
影响版本:
Apache Shiro <= 1.2.4
1.5 漏洞修复
获取Apache Shiro最新版本,下载链接:https://shiro.apache.org/download.html
2.漏洞复现
2.1 环境拓扑
2.2 应用协议
8080/HTTP
2.3 复现过程
基于Windows平台,使用环境
目录下的shirodemo-1.0-SNAPSHOT.jar
环境,执行java -jar shirodemo-1.0-SNAPSHOT.jar
启动环境。效果如图
运行sniper
工具箱,填写表单信息,输入ipconfig
命令,点击Attack,效果如图。
有回显信息的exp插件
无回显信息的exp插件
3.漏洞分析
3.1 详细分析
3.1.1 漏洞利用过程
使用ysoserial生成存在恶意命令的反序列化payload。使用AES默认秘钥,生成的payload进行AES/CBC/PKCS5Padding
模式加密,将加密后的结果传入到HTTP头部Cookie字段的rememberMe参数,通过HTTP协议发起请求。
注:由于Shiro重写了resolveClass方法,将原生方法中的forName方法替换为loadClass方法,由于loadClass无法加载数组类型的类,因此存在Transformer[]类的CommonCollections gadget无法成功利用此漏洞,(例如ysoserial CommonCollections1、CommonCollections3)
3.1.2 代码分析
传入的payload首先被服务器接收,并传送给Shiro拦截器处理(org.apache.shiro.web.servlet.OncePerRequestFilter#doFilter方法作为入口)
调用createSubject方法创建Subject
在org.apache.shiro.mgt.DefaultSecurityManager#getRememberedIdentity方法调用getRememberedSerializedIdentity方法获取rememberMe认证的序列化数据。
在此方法中可以获取传入的HTTP请求,响应数据,并解析获取http请求中的cookie字段值(payload所在字段)(base64形式)。
并在后续的程序中进行base64解码,将解码后的byte流存储在decoded变量中,作为后续进行AES解密的密文。
返回的密文数据流将会在org.apache.shiro.crypto.JcaCipherService#decrypt方法中完成AES解密操作。
获得的AES解密后的明文将在org.apache.shiro.web.mgt.AbstractRememberMeManager#deserialize方法中反序列化,执行其中的恶意代码。
3.1.3 漏洞触发过程
3.1.4补丁分析
对比Shiro 1.2.4与1.2.5版本的改动,在org.apache.shiro.mgt.AbstractRememberMeManager类中声明秘钥的方式,从原来的硬编码更改为自动生成秘钥。
新版本中调用generateNewKey方法进行秘钥生成,通过传入秘钥长度,根据传入的长度参数,返回对应长度的随机秘钥。
由于秘钥的随机性,使攻击者无法轻易的通过使用秘钥加密恶意序列化数据的方式进行攻击,从而修复了此漏洞。
1.漏洞信息
1.1 漏洞简介
1.2 漏洞概述
Apache Shiro 1.4.2之前的版本默认使用AES/CBC/PKCS5Padding模式加密,开启RememberMe功能的Shiro组件将允许远程攻击者构造序列化数据,通过Padding Oracle Attack进行爆破,即使在秘钥未知的条件下,也可以在目标服务器上执行任意命令。
1.3 漏洞利用条件
1.4 漏洞影响
影响版本:
Apache Shiro < 1.4.2
1.5 漏洞修复
获取Apache Shiro最新版本,下载链接:https://shiro.apache.org/download.html
2.漏洞复现
2.1 环境拓扑
2.2 应用协议
8080/HTTP
2.3 复现过程
基于Windows平台,使用环境
目录下的apache-tomcat-8.5.2
环境,进入bin目录下运行startup.bat
,linux环境运行startup.sh
启动环境。效果如图
运行sniper
工具箱,填写表单信息,点击Attack,效果如图。
![]
3.漏洞分析
3.1 详细分析
3.1.1 漏洞利用过程
使用ysoserial生成存在恶意命令的反序列化payload。获取一个用户的Cookie字段值(可以获取普通用户的Cookie)。取出rememberMe字段值,通过逐位爆破,构造正常padding数据,最终将每一块构造的block拼接在一起,形成任意构造的明文gadget加密后的密文。
注:由于Shiro重写了resolveClass方法,将原生方法中的forName方法替换为loadClass方法,由于loadClass无法加载数组类型的类,因此存在Transformer[]类的CommonCollections gadget无法成功利用此漏洞,(例如ysoserial CommonCollections1、CommonCollections3)
3.1.2 代码分析
CVE-2019-12422进行反序列化攻击的原理与CVE-2016-4437漏洞原理一致,而由于Shiro在1.2.4以上的版本已经将硬编码秘钥更换为随机生成的秘钥,因此无法获取到AES加密秘钥。CVE-2019-12422漏洞利用了AES CBC模式加密的漏洞,绕过了使用AES秘钥加密明文gadget的前提条件。具体原理如下:
在进行AES解密时,会对生成的明文进行padding校验,在com.sun.crypto.provider.CipherCore#doFinal中获取padding长度
com.sun.crypto.provider.PKCS5Padding#unpad方法进行padding检测,如果检测失败,返回-1状态码
当校验失败时,com.sun.crypto.provider.CipherCore#doFinal方法抛出异常 最终程序走到org.apache.shiro.web.servlet#removeFrom方法中,构造响应信息,进行回显。在代码中可以看见padding失败后会回显rememberMe deleteMe等特征字符串。可以作为padding oracle attack回显特征进行爆破。
攻击者可以凭借此特征,分组爆破。由于AES CBC模式解密时会将前一块的密文作为本轮解密的IV值,当密文可控时,可以逐位爆破前一块密文,根据padding error的回显不同,获取padding字符为\x10*16的IV'值。
IV ^ middle = 16 * \x10
IV' ^ middle = 构造gadget密文块
IV' = 构造gadget密文块 ^ 16 * \x10 ^IV
根据公式,循环获取所有密文块。通过这种方式,获取任意明文对应的密文。
3.1.3 漏洞触发过程
3.1.4补丁分析
对比Shiro 1.4.1与1.4.2版本的改动,在org.apache.shiro.crypto.AesCipherService类中声明AES加密的模式,从原来的CBC模式更改为GCM模式。
由于AES加密模式更换,攻击者无法使用padding oracle attack进行攻击,从而修复了此漏洞。
技术背景
拦截器和过滤器的详细区别
拦截器和过滤器的执行顺序
Shiro拦截器:
NameableFilter: NameableFilter给Filter起个名字,如果没有设置默认就是FilterName
当我们组装拦截器链时会根据这个名字找到相应的拦截器实例
OncePerRequestFilter: 用于防止多次执行Filter,一次请求只会走一次Filter链。enabled
属性:是否开启该拦截器,默认enabled=true
表示开启
ShiroFilter: 整个Shiro的入口点,用于拦截需要安全控制的请求进行处理。
AdviceFilter: 提供AOP风格的支持,类似于SpringMVC中的Interceptor
。
PathMatchingFilter: 提供基于Ant风格的请求路径匹配功能及拦截器参数解析的功能,如roles[admin,user]
自动根据,
分割解析到一个路径参数配置并绑定到相应的路径。
AccessControlFilter: 提供访问控制的基础功能,比如是否允许访问/当访问拒绝时如何处理等。isAccessAllowed表示是否允许访问;mappedValue就是[urls]配置中拦截器参数部分,如果允许访问返回true,否则false。onAccessDenied 表当访问拒绝时是否已经处理,如果返回true表示需要继续处理,如果返回false表示该拦截器实例已经处理了,直接返回即可。
1.漏洞信息
1.1 漏洞简介
1.2 漏洞概述
Apache Shiro 1.5.2之前的版本,由于Shiro拦截器与requestURI的匹配流程与Web框架的拦截器的匹配流程有差异,攻击者构造一个特殊的http请求,可以绕过Shiro的认证,未授权访问敏感路径。此漏洞有两种攻击方式,第一种攻击方式适用于Shiro < 1.5.0版本,由于Shiro 1.5.0版本修复补丁考虑不全面,导致补丁绕过,出现了第二种攻击方式,适用于Shiro < 1.5.2版本。
1.3 漏洞利用条件
1.4 漏洞影响
影响版本:
Apache Shiro < 1.6.0
1.5 漏洞修复
获取Apache Shiro最新版本,下载链接:https://shiro.apache.org/download.html
2.漏洞复现
2.1 环境拓扑
2.2 应用协议
8080/HTTP
2.3 复现过程
基于Windows平台,使用环境
目录下的shiro-basic.zip
环境,解压后,用Idea打开shiro-basic文件夹,下载maven资源,运行ShiroBasicApplication类,即可启动环境。效果如图。
Shiro < 1.5.0
构造HTTP请求,发送到服务器,完成身份验证绕过,效果如图
Shiro < 1.5.2
3.漏洞分析
3.1 详细分析
3.1.1 漏洞利用过程
Shiro < 1.5.0
获取到无权限访问的敏感路径,在路径的结尾添加/
进行身份验证绕过。
Shiro < 1.5.2
获取到无权限访问的敏感路径,在auth认证路径中的添加;
进行身份验证绕过。
3.1.2 代码分析
Shiro < 1.5.0
传入的payload首先被服务器接收,并传送给Shiro拦截器处理(org.apache.shiro.web.servlet.OncePerRequestFilter#doFilter方法作为入口)。
调用createSubject方法创建Subject,并调用execute方法进入Shiro FilterChain中。
进入org.apache.shiro.web.filter.mgt.PathMatchingFilterChainResolver#getChain方法中,首先获取请求uri路径,之后迭代获取拦截器的表达式。
这里我们重点关注/hello/*
表达式。代码进入pathMatches方法,最终调用org.apache.shiro.util.AntPathMatcher#doMatch方法进行传入的requestURI与拦截器表达式进行匹配。
匹配过程中,分别将拦截器表达式与requestURI以/
作为分隔符进行字符串到数组的转换,通过循环匹配数组中对应的元素,判断requestURI是否符合拦截器表达式匹配形式。
如果表达式中存在通配符*
,会将containsStar标志位赋值为true,进入 else if (patIdxEnd == 0)
判断条件,返回true。
继续跟进代码,在requestURI与拦截器表达式匹配结束后,还会进行一次判断,而漏洞产生的原因也是由于判断的条件。如果Shiro拦截器表达式不以/
结尾,且requestURI以/
结尾,判断代码将返回false表示匹配失败,从而绕过Shiro认证。
跟进到Spring处理URI的代码,进入org.springframework.web.servlet.handler.AbstractHandlerMethodMapping#getHandlerInternal方法,获取requestURI。
进入lookupHandlerMethod方法,调用addMatchingMappings方法,获取Spring拦截器。
进入org.springframework.web.servlet.mvc.condition.PatternsRequestCondition#getMatchingCondition方法调用doMatch方法进行requestURI和拦截器表达式的匹配。
Spring拦截器匹配流程和Shiro大致相同,都是将字符串转换为数组进行匹配。
由于Spring多了一个环节,在检测拦截器表达式与requestURI结尾是否为/
之后,并没有直接返回false。而是将拦截器表达式结尾添加/
,并继续进行path.startsWith(this.pathSeparator) != pattern.startsWith(this.pathSeparator)
测试,从而完成了拦截器表达式与requestURI的匹配。
上述攻击方式在Shiro 1.5.0版本中修复,但是被二次绕过,绕过分析如下。
Shiro < 1.5.2
Shiro 1.5.0 - 1.5.1在认证过程中基本没有变化,主要分析一下二次绕过的利用点。还是以org.apache.shiro.web.filter.mgt.PathMatchingFilterChainResolver#getChain作为起点。
在获取requestURI时,依旧会在getPathWithinApplication方法中调用getRequestUri方法进行requestURI的解析并获取,但是在URI正规化处理时,先调用decodeAndCleanUriString方法进行路径的解码,并清理URI。
进入decodeAndCleanUriString方法,发现此方法会以分号将传入的URI进行截断,并将分号以及分号后面的数据进行清空,返回分号前面的URI数据,从而让/a/b;/c
变为/a/b。
继续跟进到Spring拦截器的decodeAndCleanUriString方法中
从代码中可以发现,Spring对于分号处理的方式与Shiro不同,Spring会先获取分号的位置,并检测分号后是否存在/
,如果有,将/
的位置记录在slashIndex变量中,并将分号前的数据与/
之后的数据进行拼接,从而让/a/b;/c
变为/a/b/c
。返回处理后的requestURI。
由于Spring与Shiro的decodeAndCleanUriString方法不同,攻击者可以使用分号构造路径,绕过Shiro认证,并可以匹配Spring的动态控制器。
3.1.3 漏洞触发过程
Shiro < 1.5.0
Shiro < 1.5.2
3.1.4补丁分析
对比Shiro 1.4.2与Shiro 1.5.0版本的改动,在org.apache.shiro.web.filter.PathMatchingFilter类中添加了删除requestURI结尾的/
的代码。
由于requestURI和拦截器表达式结尾的/
都会被清除,因此防御了此漏洞。
对比Shiro 1.5.1与Shiro 1.5.2版本的改动 由于Shiro 1.5.2版本中,在进行decodeAndCleanUriString方法之前会先进行uri解析,调用request.getServletPath()和request.getPathInfo()获取ServletPath 和PathInfo 并进行路径拼接,避开了decodeAndCleanUriString对于分号的处理,从而修复了此漏洞。
1.漏洞信息
1.1 漏洞简介
1.2 漏洞概述
Apache Shiro 1.5.3之前的版本,由于Shiro拦截器与requestURI的匹配流程与Web框架的拦截器的匹配流程有差异,攻击者构造一个特殊的http请求,可以绕过Shiro的认证,未授权访问敏感路径。此漏洞存在两种攻击方式。
1.3 漏洞利用条件
First Attack
Second Attack
1.4 漏洞影响
影响版本:
Apache Shiro < 1.5.3
1.5 漏洞修复
获取Apache Shiro最新版本,下载链接:https://shiro.apache.org/download.html
2.漏洞复现
2.1 环境拓扑
2.2 应用协议
8080/HTTP
2.3 复现过程
基于Windows平台,使用环境
目录下的shiro-basic.zip
环境,解压后,用Idea打开shiro-basic文件夹,下载maven资源,运行ShiroBasicApplication类,即可启动环境。效果如图。
First Attack
构造HTTP请求,发送到服务器,完成身份验证绕过,效果如图
Second Attack
构造HTTP请求,发送到服务器,完成身份验证绕过,效果如图
3.漏洞分析
3.1 详细分析
3.1.1 漏洞利用过程
First Attack
获取到无权限访问的敏感路径,在authc认证路径后添加%25%32%66
进行身份验证绕过。
Second Attack
获取到无权限访问的敏感路径,在路径的头部添加/;/
进行身份验证绕过。
3.1.2 代码分析
First Attack
传入的payload首先被服务器接收,并传送给Shiro拦截器处理(org.apache.shiro.web.servlet.OncePerRequestFilter#doFilter方法作为入口)。
调用createSubject方法创建Subject,并调用execute方法进入Shiro FilterChain中。
进入org.apache.shiro.web.filter.mgt.PathMatchingFilterChainResolver#getChain方法中,首先获取请求uri路径。 在Shiro1.5.2版本中,对于requestURI处理的方式存在一些不同,此处也是漏洞触发点所在。Shiro1.5.2使用的是request.getContextPath(),request.getServletPath(),request.getPathInfo()拼接的方式。由于getServletPath()方法会对requestURI进行一次url解码,在之后的decodeAndCleanUriString方法中进行第二次url解码。
回到getChain方法中,迭代获取拦截器的表达式。
这里重点关注/hello/*
表达式。代码进入pathMatches方法,最终调用org.apache.shiro.util.AntPathMatcher#doMatch方法进行传入的requestURI与拦截器表达式进行匹配。
匹配过程中,分别将拦截器表达式与requestURI以/
作为分隔符进行字符串到数组的转换,通过循环匹配数组中对应的元素,判断requestURI是否符合拦截器表达式匹配形式。
如果表达式中存在通配符*
,会将containsStar标志位赋值为true,进入 else if (patIdxEnd == 0)
判断条件,返回true。
最终回到doMatch方法中,通过判断表达式数组的元素个数与requestURI的元素个数,以及表达式中是否包含**
,完成后续的匹配。
跟进到Spring处理URI的代码,进入org.springframework.web.servlet.handler.AbstractHandlerMethodMapping#getHandlerInternal方法,获取requestURI。由于Spring获取requestURI时使用getRequestURI()方法,此方法不会进行URL解码。只会在decodeAndCleanUriString完成一次url解码。
进入lookupHandlerMethod方法,调用addMatchingMappings方法,获取Spring拦截器。
进入org.springframework.web.servlet.mvc.condition.PatternsRequestCondition#getMatchingCondition方法调用doMatch方法进行requestURI和拦截器表达式的匹配。
Spring拦截器匹配流程和Shiro大致相同,同样是将字符串转换为数组进行匹配。
由于Spring只进行了一次URL解码,所以将未完全解码的部分作为一个整体,从而完成了拦截器表达式与requestURI的匹配。
Second Attack
漏洞触发点同样是Shiro在修复CVE-2020-1957漏洞时,使用request.getContextPath(),request.getServletPath(),request.getPathInfo()拼接的方式,进行requestURI的获取。
直接跟踪到uri = valueOrEmpty(request.getContextPath()) + "/" + valueOrEmpty(request.getServletPath()) + valueOrEmpty(request.getPathInfo());
在调用getContextPath()方法获取context-path时,会调用removePathParameter方法清除掉分号以及分号到下一个/中间的数据。
接下来进入for循环中匹配candidate与conotext-path是否相同 如果不同,则从传入的URL中继续读取下一级目录,直到condidate与context-path相同,返回从URL截取的目录作为contextPath。由于context-path获取方式和removePathparameters方法对URL的处理,攻击者可以请求,让contextPath变量获取到带有分号的非预期值。
在进行requestURI拼接时,构造出根路径带有分号的requestURI。利用CVE-2020-1957漏洞原理,经过decodeAndCleanUriString方法时,截断reqeustURI中分号后的数据,并返回。从而绕过了shiro权限控制。
3.1.3 漏洞触发过程
First Attack
Second Attack
3.2.4补丁分析
对比Shiro 1.5.2与Shiro 1.5.3版本的改动,在org.apache.shiro.web.util.WebUtils类中添加了删除requestURI结尾的/
的代码。
补丁主要优化了getPathWithinApplication方法,并单独定义了getServletPath方法,getPathInfo方法。补丁修复后,调用getPathWithinApplication方法获取requestURI只会在进行getServletPath方法中进行一次url解码,保持与Spring获取requestURI过程中相同的url解码次数。防御了双重url编码绕过。
获取requestURI直接调用getServletPath方法和getPathInfo方法进行拼接,由于不需要与contextpath拼接,从而防御了First Attack攻击。
1.漏洞信息
1.1 漏洞简介
1.2 漏洞概述
Apache Shiro 1.6.0之前的版本,由于Shiro拦截器与requestURI的匹配流程与Web框架的拦截器的匹配流程有差异,攻击者构造一个特殊的http请求,可以绕过Shiro的认证,未授权访问敏感路径。
1.3 漏洞利用条件
1.4 漏洞影响
影响版本:
Apache Shiro < 1.6.0
1.5 漏洞修复
获取Apache Shiro最新版本,下载链接:https://shiro.apache.org/download.html
2.漏洞复现
2.1 环境拓扑
2.2 应用协议
8080/HTTP
2.3 复现过程
基于Windows平台,使用环境
目录下的shiro-basic.zip
环境,解压后,用Idea打开shiro-basic文件夹,下载maven资源,运行ShiroBasicApplication类,即可启动环境。效果如图。
构造HTTP请求,发送到服务器,完成身份验证绕过,效果如图
3.漏洞分析
3.1 详细分析
3.1.1 漏洞利用过程
获取到无权限访问的敏感路径,在authc认证路径后添加%3b进行身份验证绕过。
3.1.2 代码分析
传入的payload首先被服务器接收,并传送给Shiro拦截器处理(org.apache.shiro.web.servlet.OncePerRequestFilter#doFilter方法作为入口)。
调用createSubject方法创建Subject,并调用execute方法进入Shiro FilterChain中。
进入org.apache.shiro.web.filter.mgt.PathMatchingFilterChainResolver#getChain方法中,首先获取请求uri路径。
在Shiro1.5.3版本中,对于requestURI处理的方式存在一些不同,虽然Shiro官方在此处做了很多优化,但是依然存在与Spring处理方式不一致的请求,导致漏洞的产生。在Shiro1.5.3版本中,requestURI直接调用getServletPath方法和getPathInfo方法进行拼接。并清除解码后的requestURI中分号之后的数据。
回到getChain方法中,迭代获取拦截器的表达式。
这里重点关注/hello/*
表达式。代码进入pathMatches方法,最终调用org.apache.shiro.util.AntPathMatcher#doMatch方法进行传入的requestURI与拦截器表达式进行匹配。
匹配过程中,分别将拦截器表达式与requestURI以/
作为分隔符进行字符串到数组的转换,通过循环匹配数组中对应的元素,判断requestURI是否符合拦截器表达式匹配形式。
由于解码后的requestURI被分号切割,导致pathDirs数组的元素个数少于pattDirs数组的元素个数。导致拦截器表达式与requestURI匹配失败,绕过认证
跟进到Spring处理URI的代码,进入org.springframework.web.servlet.handler.AbstractHandlerMethodMapping#getHandlerInternal方法,获取requestURI。由于Spring获取requestURI时使用getRequestURI()方法,此方法不会进行URL解码。
进入decodeAndCleanUriString方法,对未进行url解码的requestURI调用removeSemicolonContent方法进行分号截断操作。由于此时的requestURI处于未解码状态,因此编码后的分号是无法被解析的。
进入lookupHandlerMethod方法,调用addMatchingMappings方法,获取Spring拦截器。
进入org.springframework.web.servlet.mvc.condition.PatternsRequestCondition#getMatchingCondition方法调用doMatch方法进行requestURI和拦截器表达式的匹配。
Spring拦截器匹配流程和Shiro大致相同,同样是将字符串转换为数组进行匹配。
由于Spring对requestURI的先进行分号分割操作,再进行了一次URL解码。所以编码后分号之后的数据并不能被有效的分割,进行URL解码后,分号以及分号之后的数据成为了一个新的目录,从而完成了拦截器表达式与requestURI的匹配。
总结:Shiro对于requestURI的处理,先进行URL解码,再进行分号分割。Spring对于requestURI的处理,先进行分号分割,再进行URL解码。二者在解析requestURI时存在差异,因此导致漏洞出现。
3.1.3 漏洞触发过程
3.2.4补丁分析
对比Shiro 1.5.3与Shiro 1.6.0版本的改动,在Shiro 1.6.0版本中新增org.apache.shiro.web.filter.InvalidRequestFilter类。
在InvalidRequestFilter类中定义了SEMICOLON和BACKSLASH变量分别匹配路径中的;
和\
以及URL编码特征。调用isAccessAllowed方法,分别调用containsSemicolon,containsBackslash,containsNonAsciiCharacters方法进行;
和\
和不可见字符的检测。如果上述三个特征存在任意一个,则返回400状态。
在org.apache.shiro.web.filter.mgt.DefaultFilter类中添加InvalidRequestFilter拦截器。在org.apache.shiro.web.config.IniFilterChainResolverFactory类中添加/**拦截器表达式,并为所有拦截器表达式赋予invalidRequest,目的是让所有传入的路径都可以经过InvalidRequestFilter检测。
1.https://www.infoq.com/articles/apache-shiro/
2.https://zhuanlan.zhihu.com/p/54176956
3.https://github.com/apache/shiro
4.https://www.cnblogs.com/luop/p/4334160.html
5.http://blog.orange.tw/2018/03/pwn-ctf-platform-with-java-jrmp-gadget.html<br
6.https://cloud.tencent.com/developer/article/1367702
7.https://www.cnblogs.com/geektcp/p/10061908.html
本文由 Seebug Paper 发布,如需转载请注明来源。本文地址:https://paper.seebug.org/1378/