浅谈Spring Security身份验证绕过漏洞(CVE-2023-34034)

Spring官方发布了CVE-2023-34034,当Spring Security使用**作为匹配模式时,在SpringSecurity与SpringWebFlux之间会发生模式不匹配,最终可能导致身份认证绕过。

0x00 前言

前段时间,Spring官方发布了Spring Framework 身份认证绕过漏洞(CVE-2023-20860),当Spring Security使用mvcRequestMatcher配置并将**作为匹配模式时,在Spring Security 和 Spring MVC 之间会发生模式不匹配,最终可能导致身份认证绕过。

漏洞原理也比较简单,主要是mvcRequestMatcher的问题。主要是其在在比对用户配置的权限pattern与请求path是否一致时,与Spring MVC的方式存在差异(mvcRequestMatcher在调用PathPattern的match方法之前没有判断pattern是否以/开头,如果不是的话进行补全),导致了某些场景下存在权限绕过。

具体分析可见https://forum.butian.net/share/2199

0x01 漏洞描述

主要是SpringSecurity与SpringWebFlux之间的差异导致的绕过问题:
image.png

1.1 影响版本

Spring Security:

  • 6.1.0 to 6.1.1
  • 6.0.0 to 6.0.4
  • 5.8.0 to 5.8.4
  • 5.7.0 to 5.7.9
  • 5.6.0 to 5.6.11

0x02 原理分析

漏洞的产生主要在在Spring WebFlux 和 Spring MVC 之间模式不匹配。对比两者的解析过程:

2.1 SpringSecurity对Spring WebFlux的支持

首先看看SpringSecurity是怎么支持Spring WebFlux的。

SpringSecurity对WebFlux的支持主要依赖于 WebFilter

具体可以参考https://springdoc.cn/spring-security/reactive/configuration/webflux.html

首先创建一个配置类来定义安全规则。使用@EnableWebFluxSecurity注解启用WebFlux安全配置,并通过实现SecurityFilterChain来定义安全规则链。然后使用ServerHttpSecurity配置类来定义授权规则。通过authorizeExchange()方法来为不同的请求路径和HTTP方法定义授权要求。例如下面的例子:

@EnableWebFluxSecurity
public class SecurityConfig {

    @Bean
    public SecurityWebFilterChain securityWebFilterChain(ServerHttpSecurity http) {
        return http
            .authorizeExchange()
                .pathMatchers("/public/**").permitAll()
                .pathMatchers("/admin/**").hasRole("ADMIN")
                .anyExchange().authenticated()
            .and()
            .build();
    }
}

使用pathMatchers来定义不同的请求路径模式,并使用相应的权限规则。例如,/public/路径模式允许所有用户访问,/admin/路径模式要求用户具有"ADMIN"角色,而对于其他任意请求路径,则要求用户进行身份验证。

2.2 Spring WebFlux解析过程

在Spring WebFlux中,具体的解析过程可以参考https://forum.butian.net/share/2317

核心是调用org.springframework.web.reactive.result.condition.PatternsRequestCondition#getMatchingPatterns方法进行相关的匹配:

image.png

这里首先会从exchange对象中获取请求的路径信息并赋值给lookupPath,然后通过PathPattern的方式进行路径匹配:

image.png

匹配的pattern是从org.springframework.web.reactive.result.method.RequestMappingInfo的patternsCondition属性获取的,所以需要看看RequestMappingInfo的实例化过程:

image.png

在Spring WebFlux中,RequestMappingHandlerMapping是一个用于映射请求到处理方法的处理器映射器。它负责确定给定请求的处理方法,并返回与该请求最匹配的映射信息。

getMappingForMethod方法是RequestMappingHandlerMapping类中的一个方法,其作用是为给定的处理方法(HandlerMethod)获取与之匹配的请求映射(RequestMappingInfo):

image.png

首先调用AnnotatedElementUtils.findMergedAnnotation方法获取element上的RequestMapping注解。这个注解可以用于定义请求路径、请求方法、请求参数等信息。最后根据获取到的RequestMapping注解和自定义条件,调用createRequestMappingInfo方法创建一个完整的请求映射信息(RequestMappingInfo)对象,并返回:

image.png

继续跟进具体的过程,调用builder.options(this.config).build()方法,使用提供的配置(this.config)构建并返回最终的请求映射信息(RequestMappingInfo)对象:

image.png

这里可以看到对RequestMappingInfo进行了实例化,通过包括了前面提到的PatternsRequestCondition的处理:

image.png

跟进parse方法,可以看到这里回到patterns进行处理,如果不是以/开头的话,会进行补全:

image.png

也就是说跟Spring MVC一样,对于类似如下的Controller,同样可以正常解析:

@RequestMapping("admin")
public class AdminController {

    @RequestMapping("/page")
    public String manage() {
        return "admin page";
    }
}

image.png

2.3 pathMatchers解析过程

以spring-security-web-5.7.8为例,查看具体的解析过程:

在SpringSecurity中,对于Spring WebFlux,可以使用pathMatchers来实现基于请求路径进行权限配置的功能。

查看pathMatchers的实现,可以看到这里跟PathPatternParserServerWebExchangeMatcher有关:

image.png

其会根据用户配置创建基于路径匹配的 ServerWebExchangeMatcher 对象。首先创建一个空的 matchers 列表,用于存储创建的 PathPatternParserServerWebExchangeMatcher 对象。匹配请求路径的功能是由 PathPatterParserServerWebExchangeMatcher 来实现的。其会拦截请求路径,并且提取请求路径的参数。遍历传入的 patterns 数组,并对于每个路径模式,创建一个 PathPatternParserServerWebExchangeMatcher 对象,并传入该模式和请求方法,然后添加到matchers 列表中:

image.png

查看PathPatternParserServerWebExchangeMatcher的实例化过程,其中传入的pattern属性会调用DEFAULT_PATTERN_PARSER.parse进行处理:

image.png

实际上会调用org.springframework.web.util.pattern.InternalPathPatternParser#parser进行处理:

image.png

查看具体的处理过程:

image.png

首先遍历路径模式字符串的每个字符,在遍历过程中,首先检查是否遇到路径分隔符(separator),如果是,则创建对应的 SeparatorPathElementWildcardTheRestPathElement 并添加到解析结果中:

image.png

然后是对一些特殊字符的处理,主要包括?{}:*,以: 字符为例,其表示变量捕获的正则表达式结束,会进行相应的状态更新:

image.png

如果处于变量捕获状态,则根据规则检查字符的合法性,并抛出对应的异常:

image.png

遍历完成后,将最后一个路径元素添加到解析结果中。并使用解析得到的路径模式字符串、解析器和解析结果创建并返回 PathPattern 对象,用于后续的路径匹配和处理:

image.png

整个过程中并没有判断pattern是否以/开头,如果不是的话进行补全。所以这里可能会存在跟CVE-2023-20860类似的问题。

当请求对应的path时,Spring Security会遍历前面封装好的安全配置,进行匹配:

image.png

接下来查看PathPatternParserServerWebExchangeMatcher的matches方法,该方法用来用来判断请求是否匹配。这里实际上也是使用的PathPattern进行解析,也就是说SpringSecurity在解析时跟Spring WebFlux的路径解析模式是一致的。

首先通过exchange使用 getRequest() 方法获取 ServerHttpRequest 对象,然后再通过getPath() 方法获取请求路径,然后获取当前请求的方法,判断请求方法与当前规则是否一致:

image.png

如果请求方法匹配或者没有指定请求方法,会调用 PathPattern 对象的 matches 方法,将当前请求的路径和请求方法传递给路径模式对象,进行路径匹配判断,如果匹配失败,则返回一个不匹配的结果,并在日志中记录对应的信息:

image.png

如果路径匹配成功,则使用当前的路径模式 (pattern) 提取路径变量,并将路径变量保存在 pathVariables 中:

image.png

最终返回匹配的结果供后续权限校验使用。

0x03 漏洞复现

假设当前配置类的安全规则如下,对于admin路径下的资源要求用户具有"UPDATE_USER"权限(只有admin用户登陆才会具有该权限),而对于其他任意请求路径,则要求用户进行身份验证。:

@EnableWebFluxSecurity
public class SecurityConfig {

    @Bean
    public SecurityWebFilterChain securityWebFilterChain(final ServerHttpSecurity http) {

        return http
                .csrf().disable()
                .authorizeExchange()
                .pathMatchers("/", "/login", "/logout").permitAll()
                .pathMatchers("admin/**").hasAuthority("UPDATE_USER")
                .anyExchange().authenticated()
                .and()
                .formLogin()
                .loginPage("/login")
                .and()
                .logout()
                .logoutUrl("/logout")
                .requiresLogout(ServerWebExchangeMatchers.pathMatchers(HttpMethod.GET, "/logout"))
                .and()
                .build();
    }
    @Bean
public ReactiveUserDetailsService reactiveUserDetailsService(final PasswordEncoder passwordEncoder) {
    return username -> {
        log.debug("login with username => {}", username);

        UserDetails user;
        switch (username) {
            case "admin": {
                user = User.withUsername(username)
                        .password(passwordEncoder.encode("password"))
                        .authorities(
                                () -> "CREATE_USER",
                                () -> "UPDATE_USER",
                                () -> "DELETE_USER",
                                () -> "RESET_USER_PASSWORD"
                        )
                        .build();
                break;
            }

            case "supervisor": {
                user = User.withUsername(username)
                        .password(passwordEncoder.encode("password"))
                        .authorities(
                                () -> "RESET_USER_PASSWORD"
                        )
                        .build();
                break;
            }

            default: {
                user = User.withUsername(username)
                        .password(passwordEncoder.encode("password"))
                        .authorities(Collections.emptyList())
                        .build();
            }
        }

        return Mono.just(user);
    };
}

    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }
}

对应admin目录的Controller资源如下:

@RestController
@RequestMapping("/admin")
public class AdminController {

    @GetMapping("/page")
    public String manage() {
        return "admin page";
    }
}

根据前面的分析,当对应的配置没有以/开头时,会因为解析差异导致权限绕过的问题。

根据PathPattern的matches的调用,这里会根据PathPattern的链式节点中对应的PathElement的matches方法逐个进行匹配,当安全规则的pattern为admin/*时,此时第一个Element是admin,对应LiteralPathElement#matches解析:

image.png

此时会获取path的第一个Element(如果访问的path是/admin/index,那么第一个Element是/):

image.png

/明显不是PathSegment的实例,此时匹配失败会返回false,但是Spring WebFlux却能正常解析,导致了绕过。

下面印证前面的猜想,首先以test用户登陆,其是不具有"UPDATE_USER"权限的:

image.png

此时访问目标路由,可以看到成功绕过了配置的Spring security规则,以没有"UPDATE_USER"权限的test用户身份访问了/admin/page:

image.png

0x04 修复方式

首先在PathPatternParser中添加了一个新方法,会将传入的pattern初始化成完整 URL 路径匹配的模式。

以spring-web-5.3.29为例,查看具体的实现,可以看到这里主要是对不是以/开头的情况进行补全:

image.png

在SpringSecurity中,同样进行了类似的操作,以5.8.5版本为例,在PathPatternParserServerWebExchangeMatcher的实例化过程中,同样的会调用parse方法进行处理:

image.png

可以看到这里调用了initFullPathPattern对不是以/开头的pattern情况进行补全,保证两者的解析模式是一致的,避免绕过问题:

image.png

  • 发表于 2023-08-04 09:00:02
  • 阅读 ( 9199 )
  • 分类:漏洞分析

0 条评论

请先 登录 后评论
tkswifty
tkswifty

64 篇文章

站长统计