freeBuf
主站

分类

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

特色

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

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

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

FreeBuf+小程序

FreeBuf+小程序

Shiro CVE-2021-41303 路径绕过
2025-01-27 16:57:31
所属地 广东省

漏洞描述

Apache Shiro before 1.8.0, when using Apache Shiro with Spring Boot, a specially crafted HTTP request may cause an authentication bypass.[1]

漏洞条件

  • shiro <1.8.0
  • 错误的的url配置
    假设说”/admin"目录下有一个特殊页面"/admin/anonPage"不用登入也能访问,但是却错误地配置成以下内容:
map.put("/admin/*", "authc");   //若是"/admin/**" 则漏洞无效
map.put("/admin/anonPage", "anon");  //"anon" 是anonymous的缩写,表示不用登入即刻访问

按照配置而言,"/admin/*"是包含 ”/admin/anonPage“(或"/admin/anonPage/")的,所以按正确逻辑来讲,当请求是”/admin/anonPage"(或“/admin/anonPage/")shiro只能匹配第一项,永远不会匹配第二项,也就是说第二项配置是无效的。

  • shiro是按照配置顺序匹配请求路径
  • 从逻辑来讲,"/admin/anonPage"和"/admin/anonPage/"它们是相同,springweb中的匹配路径规则是如此,shiro的匹配规则会将后者处理成前者后再匹配,最终效果也是两者相同

正确的配置是两个配置交换顺序,优先匹配特例。
但现在配置是错误的,shiro却没有做出对应“正确”的反应,即以上配置可以绕过。

漏洞复现

配置环境

基础:

shiro : 1.7.1
spring : 2.7.4

shiro

@Bean
    ShiroFilterFactoryBean getShiroFilterFactoryBean(DefaultWebSecurityManager securityManager) {
        ShiroFilterFactoryBean bean = new ShiroFilterFactoryBean();
        bean.setSecurityManager(securityManager);
        bean.setLoginUrl("/login");
        bean.setSuccessUrl("/loginSuccess");
        bean.setUnauthorizedUrl("/unauthorized");
        LinkedHashMap<String, String> map = new LinkedHashMap<String, String>();
        //url --> filter1,filter2....
        map.put("/admin/*", "authc");
        map.put("/admin/anonPage", "anon");
        map.put("/login","authc");
        bean.setFilterChainDefinitionMap(map);
        return bean;
    }

controller

@GetMapping("/admin/anonPage")
public String adminInfo() {

    return "This is an anonymous page";
}
  1. url == "/admin/anonPage":
    image
    很明显,由于没有会话id,因此此请求是匿名的(anonymous),但是"/admin/*"是"authc",是需要验证(即登入)的,因此重定向到登入页面。
  2. url == "/admin/anonPage/"
    image

漏洞分析

image

首先需要知道,自shiro1.7.1开始,PathMatchingFilterChainResolver.getChain(...)的匹配规则发生了一个比较明显的改变

shiro 1.7.0:

public FilterChain getChain(ServletRequest request, ServletResponse response, FilterChain originalChain) {
        FilterChainManager filterChainManager = getFilterChainManager();
        if (!filterChainManager.hasChains()) {
            return null;
        }

        String requestURI = getPathWithinApplication(request);

        // in spring web, the requestURI "/resource/menus" ---- "resource/menus/" bose can access the resource
        // but the pathPattern match "/resource/menus" can not match "resource/menus/"
        // user can use requestURI + "/" to simply bypassed chain filter, to bypassed shiro protect
        if(requestURI != null && !DEFAULT_PATH_SEPARATOR.equals(requestURI)
                && requestURI.endsWith(DEFAULT_PATH_SEPARATOR)) {
            requestURI = requestURI.substring(0, requestURI.length() - 1);
        }


        //the 'chain names' in this implementation are actually path patterns defined by the user.  We just use them
        //as the chain name for the FilterChainManager's requirements
        for (String pathPattern : filterChainManager.getChainNames()) {
            if (pathPattern != null && !DEFAULT_PATH_SEPARATOR.equals(pathPattern)
                    && pathPattern.endsWith(DEFAULT_PATH_SEPARATOR)) {
                pathPattern = pathPattern.substring(0, pathPattern.length() - 1);
            }

            // If the path does match, then pass on to the subclass implementation for specific checks:
            if (pathMatches(pathPattern, requestURI)) {
                if (log.isTraceEnabled()) {
                    log.trace("省略");
                }
                return filterChainManager.proxy(originalChain, pathPattern);
            }
        }

        return null;
    }

shiro 1.7.1:

public FilterChain getChain(ServletRequest request, ServletResponse response, FilterChain originalChain) {
    FilterChainManager filterChainManager = getFilterChainManager();
    if (!filterChainManager.hasChains()) {
        return null;
    }

    final String requestURI = getPathWithinApplication(request);
    final String requestURINoTrailingSlash = removeTrailingSlash(requestURI);

    //the 'chain names' in this implementation are actually path patterns defined by the user.  We just use them
    //as the chain name for the FilterChainManager's requirements
    for (String pathPattern : filterChainManager.getChainNames()) {
        // If the path does match, then pass on to the subclass implementation for specific checks:
        if (pathMatches(pathPattern, requestURI)) {
            if (log.isTraceEnabled()) {
                log.trace("....");
            }
            return filterChainManager.proxy(originalChain, pathPattern);
        } else {

            // in spring web, the requestURI "/resource/menus" ---- "resource/menus/" bose can access the resource
            // but the pathPattern match "/resource/menus" can not match "resource/menus/"
            // user can use requestURI + "/" to simply bypassed chain filter, to bypassed shiro protect

            pathPattern = removeTrailingSlash(pathPattern);

            if (pathMatches(pathPattern, requestURINoTrailingSlash)) {
                if (log.isTraceEnabled()) {
                    log.trace("....");
                }

                //漏洞所在!!!
                return filterChainManager.proxy(originalChain, requestURINoTrailingSlash);
            }
        }
    }

    return null;
}

前者是直接去除请求url和pattern末尾的slash(“/”),然后进行匹配;后者是先不去除slash匹配一次,若不匹配则去除slash后再匹配一次。

image

shiro虽然"/admin/anonPage/"没有直接匹配到 "/admin/*",但是在第二个分支,将其尾部slash去除后,是匹配上了的。那为什么明明匹配上了 "/admin/*",却还是被绕过了?

原因在于断点处(第二个分支):

filterChainManager.proxy(originalChain, requestURINoTrailingSlash);

可以看到,第二个参数,也就是要选择的chainName是去除尾部slash后的requestURINoTrailingSlash(即“/admin/anonPage”)

public FilterChain proxy(FilterChain original, String chainName) {
        NamedFilterList configured = getChain(chainName);   //底层调用Map
        if (configured == null) {
            String msg = "There is no configured chain under the name/key [" + chainName + "].";
            throw new IllegalArgumentException(msg);
        }
        return configured.proxy(original);
    }

所以在获的过滤链时获取的自然就是“/admin/anonPage”对应的过滤链(anon)

漏洞修复

其实从漏洞分析部分可知,这个漏洞本质上是一个bug。[3]

其修复逻辑如下:[2]

image

将传递的参数改为pathPattern

总结

本次漏洞比较特殊,是“在错误配置下,没有执行对应”正确“行为”类型的漏洞,算是收获了漏洞理解的一种角度。

Reference


[1] Security Reports | Apache Shiro

[2] Backport SHIRO-825 · apache/shiro@4a20bf0

[3] [SHIRO-825] Trailing slash in URI results in "IllegalArgumentException: There is no configured chain under the name/key" - ASF JIRA

[4] 文章 - Shiro 历史漏洞分析 - 先知社区

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