Apache Shiro身份验证绕过漏洞(CVE-2021-41303)分析

此漏洞爆发应急于中秋前,因为网上到现在也没看到有师傅分析这个漏洞,所以在中秋后对此漏洞进行了自我理解式的具体分析~

0x00漏洞描述

Apache Shiro是阿帕奇(Apache)基金会的一套用于执行认证、授权、加密和会话管理的Java安全框架。

近日,Apache Shiro被披露出身份验证绕过漏洞,攻击者使用特制的 HTTP 请求可进行身份验证绕过。

0x01影响版本

Apache Shiro < 1.8.0

0x02环境搭建

基本环境采用:https://github.com/lenve/javaboy-code-samples/tree/master/shiro/shiro-basic ,拉入idea前修改shiro版本为1.7.1

配置路径拦截器,服务启动时该配置会被当做规则一样写入filterChains中。测试环境暂时最好以以下顺序和路径进行配置,原因是在shiro的鉴权配置中,使用的LinkedHashMap是一个有序的HashMap,而我认为shiro的认证鉴权会根据配置的先后顺序去依次拦截

配置访问路由

debug启动服务之后,访问 http://localhost:8080/admin/vv/page/

0x03漏洞分析

首先在PathMatchingFilterChainResolver类中断点getChain方法
fiterChains是配置过的拦截器,当中有对访问路径做匹配拦截的规则,而request当中的coyoteRequest就是现在url所访问的路径

经过 getPathWithinApplicationWebUtils.getPathWithinApplication 方法得到 requestURL/admin/vv/page/

removeTrailingSlash 对路径末尾做做反斜杠的删除得到

由于拦截器的顺序,URL会经过pathMatches先匹配/admin/*/page拦截规则

        String pathPattern;
        do {
            if (!var7.hasNext()) {
                return null;
            }
            pathPattern = (String)var7.next();
            if (this.pathMatches(pathPattern, requestURI)) {
                if (log.isTraceEnabled()) {
                    log.trace("Matched path pattern [{}] for requestURI [{}].   Utilizing corresponding filter chain...", pathPattern,  Encode.forHtml(requestURI));
                }
......
protected boolean pathMatches(String pattern, String path) {
    PatternMatcher pathMatcher = this.getPathMatcher();
    return pathMatcher.matches(pattern, path);
}

matches --> match --> 然后来到AntPathMatcher类中的doMatch方法,先通过StringUtils.tokenizeToStringArray方法将路径以“/”对其拆分成数组

在这里对数组中的每个字符做*和强对比的循环

正因为URI后多加一个/,就能让requestURIpathPattern匹配不上,直接进入else,并且能在else中的if使其pathPatternrequestURINoTrailingSlash成功匹配上。所以根据第一个拦截器的匹配不成功则又来到doMatch匹配第二个拦截器。

protected boolean pathsMatch(String path, ServletRequest request) {
    String requestURI = this.getPathWithinApplication(request);
    log.trace("Attempting to match pattern '{}' with current requestURI '{}'...",  path, Encode.forHtml(requestURI));
    boolean match = this.pathsMatch(path, requestURI);
    if (!match) {
        if (requestURI != null && !"/".equals(requestURI) &&  requestURI.endsWith("/")) {
            requestURI = requestURI.substring(0, requestURI.length() - 1);
        }
        if (path != null && !"/".equals(path) && path.endsWith("/")) {
            path = path.substring(0, path.length() - 1);
        }
        log.trace("Attempting to match pattern '{}' with current requestURI  '{}'...", path, Encode.forHtml(requestURI));
        match = this.pathsMatch(path, requestURI);
    }
    return match;
}

protected boolean pathsMatch(String pattern, String path) {
    boolean matches = this.pathMatcher.matches(pattern, path);
    log.trace("Pattern [{}] matches path [{}] => [{}]", new Object[]{pattern,  path, matches});
    return matches;
}

protected boolean preHandle(ServletRequest request, ServletResponse response)  throws Exception {
    if (this.appliedPaths != null && !this.appliedPaths.isEmpty()) {
        Iterator var3 = this.appliedPaths.keySet().iterator();
        String path;
        do {
            if (!var3.hasNext()) {
                return true;
            }
            path = (String)var3.next();
        } while(!this.pathsMatch(path, request));
        log.trace("Current requestURI matches pattern '{}'.  Determining filter  chain execution...", path);
        Object config = this.appliedPaths.get(path);
        return this.isFilterChainContinued(request, response, path, config);
    } else {
        if (log.isTraceEnabled()) {
            log.trace("appliedPaths property is null or empty.  This Filter will  passthrough immediately.");
        }
        return true;
    }
}

以上代码对/admin/vv/page/做了末尾“/”的删除后得到/admin/vv/page又重新进入拦截器进行路径匹配

与拦截器匹配成功则返回matches为true

由于mtahces为true则代表拦截器匹配成功,故最后来到在AdviceFilter类中doFilterInternal方法,并且输出指定路由的页面信息

而访问 http://localhost:8080/admin/vv/page 是无法成功匹配至拦截器路径,因此以上的绕过则是利用“/”达到访问权限的绕过

以下为漏洞利用时函数调用栈的全部内容

doMatch:139, AntPathMatcher (org.apache.shiro.util)
match:97, AntPathMatcher (org.apache.shiro.util)
matches:93, AntPathMatcher (org.apache.shiro.util)
pathsMatch:159, PathMatchingFilter (org.apache.shiro.web.filter)
pathsMatch:127, PathMatchingFilter (org.apache.shiro.web.filter)
preHandle:195, PathMatchingFilter (org.apache.shiro.web.filter)
doFilterInternal:131, AdviceFilter (org.apache.shiro.web.servlet)
doFilter:125, OncePerRequestFilter (org.apache.shiro.web.servlet)
doFilter:66, ProxiedFilterChain (org.apache.shiro.web.servlet)
executeChain:108, AdviceFilter (org.apache.shiro.web.servlet)
doFilterInternal:137, AdviceFilter (org.apache.shiro.web.servlet)
doFilter:125, OncePerRequestFilter (org.apache.shiro.web.servlet)
doFilter:66, ProxiedFilterChain (org.apache.shiro.web.servlet)
executeChain:450, AbstractShiroFilter (org.apache.shiro.web.servlet)
call:365, AbstractShiroFilter$1 (org.apache.shiro.web.servlet)
doCall:90, SubjectCallable (org.apache.shiro.subject.support)
call:83, SubjectCallable (org.apache.shiro.subject.support)
execute:387, DelegatingSubject (org.apache.shiro.subject.support)
doFilterInternal:362, AbstractShiroFilter (org.apache.shiro.web.servlet)
doFilter:125, OncePerRequestFilter (org.apache.shiro.web.servlet)
internalDoFilter:193, ApplicationFilterChain (org.apache.catalina.core)
doFilter:166, ApplicationFilterChain (org.apache.catalina.core)
doFilterInternal:99, RequestContextFilter (org.springframework.web.filter)
doFilter:107, OncePerRequestFilter (org.springframework.web.filter)
internalDoFilter:193, ApplicationFilterChain (org.apache.catalina.core)
doFilter:166, ApplicationFilterChain (org.apache.catalina.core)
doFilterInternal:92, FormContentFilter (org.springframework.web.filter)
doFilter:107, OncePerRequestFilter (org.springframework.web.filter)
internalDoFilter:193, ApplicationFilterChain (org.apache.catalina.core)
doFilter:166, ApplicationFilterChain (org.apache.catalina.core)
doFilterInternal:93, HiddenHttpMethodFilter (org.springframework.web.filter)
doFilter:107, OncePerRequestFilter (org.springframework.web.filter)
internalDoFilter:193, ApplicationFilterChain (org.apache.catalina.core)
doFilter:166, ApplicationFilterChain (org.apache.catalina.core)
doFilterInternal:200, CharacterEncodingFilter (org.springframework.web.filter)
doFilter:107, OncePerRequestFilter (org.springframework.web.filter)
internalDoFilter:193, ApplicationFilterChain (org.apache.catalina.core)
doFilter:166, ApplicationFilterChain (org.apache.catalina.core)
invoke:200, StandardWrapperValve (org.apache.catalina.core)
invoke:96, StandardContextValve (org.apache.catalina.core)
invoke:490, AuthenticatorBase (org.apache.catalina.authenticator)
invoke:139, StandardHostValve (org.apache.catalina.core)
invoke:92, ErrorReportValve (org.apache.catalina.valves)
invoke:74, StandardEngineValve (org.apache.catalina.core)
service:343, CoyoteAdapter (org.apache.catalina.connector)
service:408, Http11Processor (org.apache.coyote.http11)
process:66, AbstractProcessorLight (org.apache.coyote)
process:836, AbstractProtocol$ConnectionHandler (org.apache.coyote)
doRun:1747, NioEndpoint$SocketProcessor (org.apache.tomcat.util.net)
run:49, SocketProcessorBase (org.apache.tomcat.util.net)
runWorker:1142, ThreadPoolExecutor (java.util.concurrent)
run:617, ThreadPoolExecutor$Worker (java.util.concurrent)
run:61, TaskThread$WrappingRunnable (org.apache.tomcat.util.threads)
run:748, Thread (java.lang)

0x04修复建议

请检查所使用的软件版本是否受在受影响范围内,并从官方渠道升级到安全版本或更新版本。Apache Shiro官方网站:https://shiro.apache.org/index.html

  • 发表于 2021-10-27 17:55:39
  • 阅读 ( 9445 )
  • 分类:漏洞分析

0 条评论

请先 登录 后评论
w1nk1
w1nk1

12 篇文章

站长统计