The Spring Framework and Spring Security are the backbone of millions of Java web apps. But, even the best software can have hidden dangers. In early 2023, a serious vulnerability (CVE-2023-20860) was found, and it affected Spring Framework versions 6.. - 6..6 and 5.3. - 5.3.25.

If you use Spring Security with the mvcRequestMatcher and double wildcards ("**"), you need to pay close attention! A pattern mismatch between Spring Security and Spring MVC can make it dangerously easy for attackers to bypass your restrictions. We'll explore how it happens, show an example setup, and walk through a real attack scenario.

1. What is the Problem?

CVE-2023-20860 describes a situation in which using "**" (double asterisk) as a URL pattern in mvcRequestMatcher for Spring Security does NOT behave the same as in Spring MVC’s pattern matching.

What does that mean?

Attackers can use this confusion to get around your intended protections.

Affected versions:

2. How Does This Vulnerability Work?

Let’s say you want to protect /admin/** so only admins go there. You might do this in your security config:

http
    .authorizeRequests()
    .requestMatchers(new MvcRequestMatcher(HandlerMappingIntrospector, "/admin/**"))
    .hasRole("ADMIN")
    .anyRequest().permitAll();

But here’s the rub

- Spring Security’s pattern matching thinks /admin/<b> should NOT match /admin (no slash after).<br>- Spring MVC’s pattern matching DOES think /admin/</b> matches /admin as well as /admin/ and /admin/foo.

So, an attacker can simply access /admin (no trailing slash), and *Spring Security* may not protect it—even though *Spring MVC* will route to your protected controller!

3. What Is the Impact?

This mismatch makes it possible for unauthorized users to access sensitive URLs you thought were locked down. For example:

- You protect /admin/<b>, but attackers get access to /admin.<br>- You meant to protect /api/private/</b>, but hackers get to /api/private.

4. Exploit Scenario: Step-by-Step

Here’s a concrete, minimal setup for demonstration.

1. Example Controller (Spring Boot)

@RestController @RequestMapping("/admin") public class AdminController { @GetMapping public String list() { return "Sensitive admin content"; } @GetMapping("/settings") public String settings() { return "Admin settings here"; } }

2. Vulnerable Security Config

@Configuration @EnableWebSecurity public class SecurityConfig { @Bean public SecurityFilterChain filterChain(HttpSecurity http, HandlerMappingIntrospector introspector) throws Exception { http .authorizeRequests() // Only matches /admin/ANYTHING, not /admin! .requestMatchers(new MvcRequestMatcher(introspector, "/admin/**")) .hasRole("ADMIN") .anyRequest().permitAll(); return http.build(); } }

3. How an Attacker Gets In

- Visiting http://yourserver/admin/settings? Protected as expected.
- Visiting http://yourserver/admin/? Also protected.
- But http://yourserver/admin (without trailing slash) is NOT protected due to the mismatch. If your controller still serves something (due to Spring MVC mapping), the attacker gets in—no authentication checked!

5. References

- Spring Security CVE-2023-20860 Official Advisory
- Spring Security GitHub Issue #13126
- Spring Security Docs - MvcRequestMatcher
- NVD CVE Details

6. How to Fix It

Solution:

Use the correct pattern to ensure all urls are covered.

- For full protection, add both /admin and /admin/** to your matcher!

Example

.authorizeRequests() .requestMatchers(new MvcRequestMatcher(introspector, "/admin")) .hasRole("ADMIN") .requestMatchers(new MvcRequestMatcher(introspector, "/admin/**")) .hasRole("ADMIN") .anyRequest().permitAll();

7. Final Thoughts

CVE-2023-20860 is a stark reminder: small configuration details in security frameworks can have big, unexpected consequences. If you use "**" wildcards in Spring Security (especially with mvcRequestMatcher), double-check your patterns—and upgrade your Spring (and Spring Security) versions today.

Need more info?  
- Read the official CVE advisory
- Check Spring’s migration guide

Timeline

Published on: 03/27/2023 22:15:00 UTC
Last modified on: 05/05/2023 20:15:00 UTC