CVE-2023-34034 is another authorization bypass in Spring Security. Like CVE-2022-31692 it’s nasty because it allows completely unrestricted access to supposedly protected resources. Also like CVE-2022-31692 it requires very specific configuration to be vulnerable and is easily fixed.
This post demonstrates the vulnerability, the problem configuration and suggested fixes. A demonstration vulnerable application is on GitHub.
The vulnerability
Consider this security configuration for a WebFlux application:
@Configuration
@EnableWebFluxSecurity
public class WebFluxSecurityConfig {
@Bean
public SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
http
// disable CSRF
.csrf().disable()
// add AuthenticationWebFilter and set the handler
.formLogin()
.authenticationSuccessHandler(new WebFilterChainServerAuthenticationSuccessHandler())
.authenticationFailureHandler(((webFilterExchange, exception) -> Mono.error(exception)))
.and()
.authorizeExchange()
.pathMatchers("admin/**")
.hasRole("ADMIN")
.and()
.authorizeExchange()
.anyExchange()
.permitAll();
return http.build();
}
Here we have a protected path (admin/**
) that requires authentication. Everything else is public (permitAll()
). With this configuration we’d expect a resource on /admin
to pop up the login form. And with later (fixed) versions of Spring Security, it does.
However, vulnerable versions of Spring Security mishandle paths without a leading /
. In Spring Security 6.0.4, the path /admin
is not matched and resources can be accessed without authentication.
Configuration
CVE-2023- is exploitable only if:
- It is a WebFlux application
- A protected path is defined without a leading slash and
- It uses a vulnerable version of Spring Security (5.6.0 to 5.6.11, 5.7.0 to 5.7.9, 5.8.0 to 5.8.4, 6.0.0 to 6.0.4 or 6.1.0 to 6.1.1
Fixes
Fortunately, remediation is simple. We can either define our paths with leading slashes or we can upgrade to a fixed version of Spring Security (5.6.12+, 5.7.10+, 5.8.5+, 6.0.5+, 6.1.2+).
Also, as general good practice, don’t use permitAll()
for unmatched paths. It’s safer to enforce security on all unmatched paths and permit only defined public pages. For example, we could allow access to all paths beginning /public/
and restrict everything else:
@Bean
public SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
http
// disable CSRF
.csrf().disable()
// add AuthenticationWebFilter and set the handler
.formLogin()
.authenticationSuccessHandler(new WebFilterChainServerAuthenticationSuccessHandler())
.authenticationFailureHandler(((webFilterExchange, exception) -> Mono.error(exception)))
.and()
.authorizeExchange()
.pathMatchers("/public/**")
.permitAll()
.and()
.authorizeExchange()
.anyExchange()
.hasRole("ADMIN");
return http.build();
}
If our pathMatcher
failed here, we would fail safe. Our public pages would become restricted but our admin pages would remain restricted.
Be First to Comment