Shiro权限绕过
0x01 简介
ApacheShiro是一个强大且易用的Java安全框架,执行身份验证、授权、密码和会话管理。使用Shiro的易于理解的API,您可以快速、轻松地获得任何应用程序,从最小的移动应用程序到最大的网络和企业应用程序。
模块
其主要存在以下四个功能模块,称为应用程序安全性的 4 个基石:
- Authentication/身份验证:证明用户身份,通常称为用户“登录”。
- Authorization/授权:访问控制,用户是否有权限访问指定URL
- Cryptography/密码学:保护或隐藏数据免遭窥探
- Session Management/会话管理:Session 管理
主要概念:
Subject
主体。每个用户登录成功后都会对应一个Subject对象,所有用户信息都存放在Subject中。可以理解:Subject就是Shiro提供的用户实体类。可通过以下代码获取当前Subject
currentUser = SecurityUtils.getSubject();
一旦获取了Subject,就可以立即访问当前用户想要使用 Shiro 执行的 90% 的操作,例如登录、注销、访问其会话、执行授权检查等等
SecurityManager
是shiro的核心容器,此容器中包含了Shiro的绝大多数功能。在非Spring Boot项目中,获取Security Manager 是编写代码的第一步。而在Spring Boot中已经帮助我们自动化配置了。
- org.apache.shiro.mgt.SecurityManager 接口定义了
createSubject
、login
和logout
三个方法用来创建 Subject、登陆和退出 - org.apache.shiro.authc.Authenticator接口定义了
authenticate
方法用来进行认证 - org.apache.shiro.authz.Authorizer接口提供了对 Permission 和 Role 的校验方法
- org.apache.shiro.session.mgt.SessionManager 接口提供了
start
、getSession
方法用来创建可获取会话
Shiro 为 SecurityManager 提供了一个包含了上述所有功能的默认实现类,对其上的方法都进行了实现
Realms
Shiro框架实现权限控制不依赖于数据库,通过内置数据也可以实现权限控制。但是目前绝大多数应用的数据都存储在数据库中,所以Shiro提供了Realms组件,此组件的作用就是访问数据库。Shiro内置的访问数据库的代码,通过简单配置就可以访问数据库,也可以自定义Realms实现访问数据库逻辑
历史漏洞
截至目前,Shiro的官网上共爆出了14个CVE漏洞,这里只复现权限绕过相关的
漏洞编号 | 影响版本 | 漏洞关键描述 |
---|---|---|
CVE-2010-3863 | Shiro < 1.1.0 & JSecurity 0.9.x | 未进行路径标准化导致权限绕过 |
CVE-2014-0074 | Shiro 1.x< 1.2.3 | 启用未经身份验证绑定的 LDAP 服务器导致权限绕过 |
CVE-2016-4437 | Shiro 1.x < 1.2.5 | Shiro 550/RememberMe 反序列化漏洞 |
CVE-2016-6802 | Shiro < 1.3.2 | Context Path 路径标准化导致绕过 |
CVE-2019-12422 | Shiro < 1.4.2 | Shiro-721/Padding Oracle Attack |
CVE-2020-1957 | Shiro < 1.5.2 & Spring | Spring 与 Shiro 对于 "/" 和 ";" 处理差异 |
CVE-2020-11989 | Shiro < 1.5.3 & Spring | Shiro 二次解码&ContextPath 使用 ";" |
CVE-2020-13933 | Shiro < 1.6.0 | URL 解码和路径标准化顺序不一致/%3b |
CVE-2020-17510 | Shiro < 1.7.0 & Spring | URL 解码和路径标准化顺序不一致/%2e |
CVE-2020-17523 | Shiro < 1.7.1 & Spring | URL 解码和路径标准化顺序不一致/%20 |
CVE-2021-41303 | Shiro < 1.8.0 & Spring Boot | 由于 Shiro 的 BUG 导致特定场景的绕过 |
CVE-2022-32532 | Shiro < 1.9.1 | RegExPatternMatcher 和 "." 误配置 |
CVE-2022-40664 | Shiro < 1.10.0 | 通过 RequestDispatcher 转发或包含 |
CVE-2023-22602 | Shiro < 1.11.0 & Spring Boot 2.6+ | 当 Shiro 和 Spring Boot 匹配差异 |
0x02 漏洞详情
CVE-2010-3863
漏洞编号:CVE-2010-3863 / CNVD-2010-2715
影响版本:Shiro < 1.1.0 & JSecurity 0.9.x
漏洞描述:将 URI 路径与 shiro.ini 文件中的条目进行比较之前不会对其进行规范化,这允许远程攻击者通过精心设计的请求绕过预期的访问限制
漏洞补丁:Commit-ab82949
漏洞分析
先看一下shiro身份认证的流程:Shiro 使用PathMatchingFilterChainResolver#getChain
方法获取和调用要执行的 Filter,看一下具体逻辑:
首先调用了WebUtils.getPathWithinApplication()方法,获取request中请求的路径,然后循环获取配置中的Antpath,然后将request的url与配置路径做匹配,如果成功就return
权限绕过就发生在getPathWithinApplication()方法,即获取url路径中,我们细看一下处理流程
首先getContextPath
方法获取 Context路径
getRequestUri()
方法获取URI
的值,并调用 decodeAndCleanUriString()
处理
decodeAndCleanUriString()
对 ;
进行了截取,其主要功能是对url 处 ;jsessionid
之类的字符串的适配。
处理之后的请求 URL 将会使用 AntPathMatcher#doMatch
进行匹配尝试。
很明显,在匹配之前,没有对路径进行标准化路径处理,那么如果 URI 中如果存在特殊字符,就可以绕过安全校验。
漏洞复现
我们自定义配置如下
ShiroFilterFactoryBean bean = new ShiroFilterFactoryBean();
bean.setSecurityManager(securityManager);
bean.setLoginUrl("/login");
bean.setUnauthorizedUrl("/unauth");
Map<String, String> map = new LinkedHashMap();
map.put("/doLogin", "anon");
map.put("/admin/**", "user");
map.put("/**", "anon");
bean.setFilterChainDefinitionMap(map);
return bean;
为了接口(/admin/\)进行了权限配置,为剩余全部的 URL (/**)设置了 anno
权限,此配置下存在校验绕过。
正常来说,访问/admin,需要认证,会因为没有权限而跳转至登陆界面
当我们添加一些特殊字符,访问 /./audit
,由于其不能与配置文件匹配,导致进入了 /**
的匹配范围,导致可以越权访问
漏洞修复
Shiro 在 ab82949 更新中添加了标准化路径函数。
该函数对对 /
、//
、/./
、/../
等进行了处理
private static String normalize(String path, boolean replaceBackSlash) {
if (path == null)
return null;
// Create a place for the normalized path
String normalized = path;
if (replaceBackSlash && normalized.indexOf('\\') >= 0)
normalized = normalized.replace('\\', '/');
if (normalized.equals("/."))
return "/";
// Add a leading "/" if necessary
if (!normalized.startsWith("/"))
normalized = "/" + normalized;
// Resolve occurrences of "//" in the normalized path
while (true) {
int index = normalized.indexOf("//");
if (index < 0)
break;
normalized = normalized.substring(0, index) +
normalized.substring(index + 1);
}
// Resolve occurrences of "/./" in the normalized path
while (true) {
int index = normalized.indexOf("/./");
if (index < 0)
break;
normalized = normalized.substring(0, index) +
normalized.substring(index + 2);
}
// Resolve occurrences of "/../" in the normalized path
while (true) {
int index = normalized.indexOf("/../");
if (index < 0)
break;
if (index == 0)
return (null); // Trying to go outside our context
int index2 = normalized.lastIndexOf('/', index - 1);
normalized = normalized.substring(0, index2) +
normalized.substring(index + 3);
}
// Return the normalized path that we have completed
return (normalized);
}
CVE-2014-0074
漏洞编号:CVE-2014-0074 / CNVD-2014-03861 / Shiro-460
影响版本:Shiro1.x < 1.2.3
漏洞描述:当使用的 LDAP 服务器启用了无需身份验证即可绑定的功能时,允许远程攻击者通过空用户名或空密码绕过身份验证。
漏洞补丁:Commit-f988846
漏洞分析
此漏洞产生的主要原因在于当LDAP 服务器启用了无需身份验证即可绑定的功能时,允许远程攻击者通过空用户名或空密码绕过身份验证,从本质来讲,此漏洞本质上应该是 ldap 的配置问题,且环境配置较复杂,就不调试分析了
漏洞修复
官方在 DefaultLdapContextFactory
和 JndiLdapContextFactory
中均加入了validateAuthenticationInfo
方法用来校验 principal 和 credential 为空的情况。可以看到这里的逻辑是只有 principal 不为空的情况下,才会对 credential 进行校验。
并在 getLdapContext
方法创建 InitialLdapContext 前执行了校验,如果为空,将会抛出异常。
但这种修复跟漏洞成因关系不一样,所以存在绕过
CVE-2016-6802
漏洞编号:CVE-2016-6802/CNVD-2016-07814
影响版本: Shiro < 1.3.2
漏洞描述:允许攻击者绕过预期的servlet过滤器,并通过使用非根servlet上下文路径获得访问权限。
漏洞补丁:Commit-b15ab92
漏洞分析
之前分析过,CVE-2010-3863漏洞产生原因在getRequestUri()
方法中,而此漏洞产生于getContextPath()
方法中
Shiro 调用 WebUtils.getPathWithinApplication()
方法获取请求路径
然后getContextPath()
方法,获取 Context 值,并调用 decodeRequestString
进行 URLDecode
由于获取的Context Path 没有标准化处理,如果是非常规的路径,例如 /./
,或者跳跃路径 /xxx/../
,都会导致在 StringUtils.startsWithIgnoreCase()
方法判断时失效,直接返回完整的 Request URI ,造成权限绕过
漏洞复现
在要请求的路径前加任意路径,再加/../
即可实现绕过
漏洞修复
在1.3.2版本的更新Commit-b15ab92中,官方进行了修复:
在 WebUtils.getContextPath
方法中,使用路径标准化方法 normalize
来处理 ContextPath
之后在进行返回。
CVE-2020-1957
漏洞编号: CVE-2020-1957 / CNVD-2020-20984 / SHIRO-682
影响版本:shiro < 1.5.2
漏洞描述:由于Shiro 和 SpringBoot 对 URL 的处理的差异化,导致越权绕过。
漏洞补丁: Commit-589f10d && Commit-9762f97 && Commit-3708d79
漏洞分析
此CVE包含了多个版本的修复与绕过,这里全部都梳理一遍
SHIRO-682
本漏洞起源于 SHIRO-682,Issues 描述了在 SpingWeb 中处理 requestURI 与 shiro 中匹配鉴权路径差异导致的绕过问题:在 Spring 中,/resource/menus
与 /resource/menus/
都可以访问资源,但是在 shiro 中,这两个路径是成功匹配的,所以在 Spring 集成 shiro 时,只需要在访问路径后添加 "/" 就存在绕过权限校验的可能。这个比较简单,不做重点
绕过
除了上面的漏洞,本 CVE 通报版本号内还存在一个另一个绕过。利用的是 shiro 和 spring 对 url 中的 ";" 处理的差别来绕过校验。我们来主要分析一下此次绕过,先看一下payload
根据之前的分析,在PathMatchingFilterChainResolver#getPathWithinApplication
处下断点,进行调试,访问url:192.168.11.1:8082/christ1na/..;/admin
首先使用getContextPath
方法,获取上下文信息,然后调用getRequestUri(request)
获取请求访问的uri
在执行normalize(decodeAndCleanUriString(request, uri));
之前,uri都为/christ1na/..;/admin
进入decodeAndCleanUriString
方法,如果uri中存在;
将进行截断,返回;
之前的字符,否则返回完整uri,经过此函数处理之后uri为/christ1na/..
然后由normalize函数进行处理,其对传入的路径进行标准化规范处理,相关操作包括替换反斜线、替换“//”为“/”等,最后得到返回的url为/christ1na/..
private static String normalize(String path, boolean replaceBackSlash) {
if (path == null) {
return null;
} else {
String normalized = path;
if (replaceBackSlash && path.indexOf(92) >= 0) {
normalized = path.replace('\\', '/');
}
if (normalized.equals("/.")) {
return "/";
} else {
if (!normalized.startsWith("/")) {
normalized = "/" + normalized;
}
while(true) {
int index = normalized.indexOf("//");
if (index < 0) {
while(true) {
index = normalized.indexOf("/./");
if (index < 0) {
while(true) {
index = normalized.indexOf("/../");
if (index < 0) {
return normalized;
}
if (index == 0) {
return null;
}
int index2 = normalized.lastIndexOf(47, index - 1);
normalized = normalized.substring(0, index2) + normalized.substring(index + 3);
}
}
normalized = normalized.substring(0, index) + normalized.substring(index + 2);
}
}
normalized = normalized.substring(0, index) + normalized.substring(index + 1);
}
}
}
}
一直执行回到PathMatchingFilterChainResolver.class#getChain,此时requesturl为/christ1na/..
接下来在while段里使用pathMatches(pathPattern, requestURI)进行权限校验,在此函数中调用了
doMatch函数进行匹配,显然/admin/**
不会与/xxx/..
匹配成功,此时成功绕过shiro的权限验证
url经过shiro的处理认证通过后,就会进入spring boot中进行解析,在UrlPathHelper#getLookupPathForRequest下断点,我们看一下spring是如何处理url的
首先调用getPathWithinServletMapping
方法对request进行解析
然后调用getPathWithinApplication
方法处理request,我们步入,可以发现其具体函数跟shiro的处理逻辑类似
我们进入getRequestUri
函数,其调用了decodeAndCleanUriString
函数对url进行处理,而shiro中也存在此函数,两者的差异就在于此
在decodeAndCleanUriString
函数中,执行了 3 个函数:removeSemicolonContent 移除分号,decodeRequestString 解码,getSanitizedPath 清理路径
而spring与shiro的差一点就在于 UrlPathHelper#removeSemicolonContent
,spring 处理了每个 / / 之间的分号,将 ";" 及之后的内容截取掉了。此时返回的url为/christ1na/../admin
布出getPathWithinApplication
方法后,经过getServletPath
方法处理,最终返回的url为/admin
漏洞验证
根spring与shiro对;
的解析差异性,有着许多种绕过的思路:
http://127.0.0.1:8080/123;/..;345/;../.;/su18/..;/;/;///////;/;/;awdwadwa/audit/list
上面这个 payload
只能在较低版本的 Spring Boot
上使用。
Ruil1n 师傅原文:
当 Spring Boot版本在小于等于 2.3.0.RELEASE的情况下,alwaysUseFullPath为默认值 false,这会使得其获取 ServletPath ,所以在路由匹配时相当于会进行路径标准化包括对%2e解码以及处理跨目录,这可能导致身份验证绕过。而反过来由于高版本将alwaysUseFullPath自动配置成了true从而开启全路径,又可能导致一些安全问题。
在高版本上不处理跨目录,就只能借助 shiro 一些配置问题尝试绕过:比如应用程序配置了访问路径 "/audit/**" 为 anon,但是指定了其中的一个 "/audit/list" 为 authc。这时在不跳目录的情况下,可以使用如下请求绕过:
http://127.0.0.1:8080/alter//;aaaa/;...///////;/;/;awdwadwa/page
漏洞修复
针对SHIRO-682 的修复,共有两个补丁
第一个 Commit-589f10d ,首先在PathMatchingFilter#pathsMatch
和PathMatchingFilterChainResolver#getChain
中增加了对访问路径后缀为 /
的支持
第二个 Commit-9762f97,在此次补丁,首先添加了endsWith
和 equals
的判断,用于修复上一次提交,导致访问路径为 /
时抛出的异常
对于SHIRO-682绕过的修复,位于补丁 Commit-3708d79,将request.getRequestURI()
获取路径更换为使用 request.getContextPath()
、request.getServletPath()
、request.getPathInfo()
进行拼接,直接获取中间件处理后的内容
Reference
https://su18.org/post/shiro-3/