Shiro权限绕过-上

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 接口定义了 createSubjectloginlogout 三个方法用来创建 Subject、登陆和退出
  • org.apache.shiro.authc.Authenticator接口定义了authenticate方法用来进行认证
  • org.apache.shiro.authz.Authorizer接口提供了对 Permission 和 Role 的校验方法
  • org.apache.shiro.session.mgt.SessionManager 接口提供了 startgetSession 方法用来创建可获取会话

Shiro 为 SecurityManager 提供了一个包含了上述所有功能的默认实现类,对其上的方法都进行了实现

image-20230706145820856

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

image-20230706220211928

权限绕过就发生在getPathWithinApplication()方法,即获取url路径中,我们细看一下处理流程

image-20230706223002861

首先getContextPath方法获取 Context路径

image-20230706223217857

getRequestUri() 方法获取URI 的值,并调用 decodeAndCleanUriString() 处理

image-20230706223551698

decodeAndCleanUriString(); 进行了截取,其主要功能是对url 处 ;jsessionid 之类的字符串的适配。

image-20230706223714502

处理之后的请求 URL 将会使用 AntPathMatcher#doMatch 进行匹配尝试。

image-20230706224245745

很明显,在匹配之前,没有对路径进行标准化路径处理,那么如果 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,需要认证,会因为没有权限而跳转至登陆界面

image-20230706225523706

当我们添加一些特殊字符,访问 /./audit,由于其不能与配置文件匹配,导致进入了 /** 的匹配范围,导致可以越权访问

image-20230706225542769

漏洞修复

Shiro 在 ab82949 更新中添加了标准化路径函数。

image-20230706225630694

该函数对对 ////.//../ 等进行了处理

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 的配置问题,且环境配置较复杂,就不调试分析了

漏洞修复

官方在 DefaultLdapContextFactoryJndiLdapContextFactory 中均加入了validateAuthenticationInfo 方法用来校验 principal 和 credential 为空的情况。可以看到这里的逻辑是只有 principal 不为空的情况下,才会对 credential 进行校验。

image-20230716192219041

并在 getLdapContext 方法创建 InitialLdapContext 前执行了校验,如果为空,将会抛出异常。

image-20230716192143105

但这种修复跟漏洞成因关系不一样,所以存在绕过

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() 方法获取请求路径

image-20230716193159442

然后getContextPath() 方法,获取 Context 值,并调用 decodeRequestString 进行 URLDecode

image-20230716193550958

由于获取的Context Path 没有标准化处理,如果是非常规的路径,例如 /./,或者跳跃路径 /xxx/../,都会导致在 StringUtils.startsWithIgnoreCase() 方法判断时失效,直接返回完整的 Request URI ,造成权限绕过

漏洞复现

在要请求的路径前加任意路径,再加/../即可实现绕过

image-20230716194608379

漏洞修复

在1.3.2版本的更新Commit-b15ab92中,官方进行了修复:

WebUtils.getContextPath 方法中,使用路径标准化方法 normalize 来处理 ContextPath 之后在进行返回。

image-20230716194915885

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

image-20230717125811337

根据之前的分析,在PathMatchingFilterChainResolver#getPathWithinApplication处下断点,进行调试,访问url:192.168.11.1:8082/christ1na/..;/admin

首先使用getContextPath方法,获取上下文信息,然后调用getRequestUri(request)获取请求访问的uri

image-20230717131105210

在执行normalize(decodeAndCleanUriString(request, uri));之前,uri都为/christ1na/..;/admin

image-20230717131423911

进入decodeAndCleanUriString方法,如果uri中存在;将进行截断,返回;之前的字符,否则返回完整uri,经过此函数处理之后uri为/christ1na/..

image-20230717131626892

然后由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/..

image-20230717133002980

接下来在while段里使用pathMatches(pathPattern, requestURI)进行权限校验,在此函数中调用了

doMatch函数进行匹配,显然/admin/**不会与/xxx/..匹配成功,此时成功绕过shiro的权限验证

image-20230717133506488

url经过shiro的处理认证通过后,就会进入spring boot中进行解析,在UrlPathHelper#getLookupPathForRequest下断点,我们看一下spring是如何处理url的

首先调用getPathWithinServletMapping方法对request进行解析

image-20230717134445534

然后调用getPathWithinApplication方法处理request,我们步入,可以发现其具体函数跟shiro的处理逻辑类似

image-20230717134820671

我们进入getRequestUri函数,其调用了decodeAndCleanUriString函数对url进行处理,而shiro中也存在此函数,两者的差异就在于此

image-20230717134926799

decodeAndCleanUriString函数中,执行了 3 个函数:removeSemicolonContent 移除分号,decodeRequestString 解码,getSanitizedPath 清理路径

image-20230717135106058

而spring与shiro的差一点就在于 UrlPathHelper#removeSemicolonContent,spring 处理了每个 / / 之间的分号,将 ";" 及之后的内容截取掉了。此时返回的url为/christ1na/../admin

image-20230717135303607

布出getPathWithinApplication方法后,经过getServletPath方法处理,最终返回的url为/admin

image-20230717135813527

漏洞验证

根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#pathsMatchPathMatchingFilterChainResolver#getChain中增加了对访问路径后缀为 / 的支持

image-20230717141433843

image-20230717141608453

第二个 Commit-9762f97,在此次补丁,首先添加了endsWithequals 的判断,用于修复上一次提交,导致访问路径为 / 时抛出的异常

image-20230717141805691

对于SHIRO-682绕过的修复,位于补丁 Commit-3708d79,将request.getRequestURI()获取路径更换为使用 request.getContextPath()request.getServletPath()request.getPathInfo() 进行拼接,直接获取中间件处理后的内容

image-20230717142202974

Reference

https://su18.org/post/shiro-3/

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

https://xz.aliyun.com/t/11633

http://rui0.cn/archives/1643

暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇