CVE-2026-22754: Spring Security Servlet-Path Authorization Bypass
Spring Security 7.0.0–7.0.4 fails to include the servlet-path when computing path matchers, silently dropping intercept-url authorization rules and enabling unauthenticated access to protected endpoints.
# A Security Flaw That Could Let Hackers Sneak Past Digital Bouncers
Imagine a nightclub with a bouncer checking names at the front door. That bouncer has a list of VIPs allowed inside and regular guests who should be turned away. But what if someone could walk around to the back entrance and the bouncer there doesn't have the same list? They'd get in anyway. That's essentially what this security flaw does in Spring Security, a widely used protection system for web applications.
Spring Security is like a security guard for websites built with Java. It controls who gets to see what information. This particular vulnerability lets attackers request sensitive information through alternate pathways that the security system wasn't properly checking. The guard at the front door knew to stop them, but the guard at the side door wasn't properly briefed.
The flaw affects websites using Spring Security versions 7.0.0 through 7.0.4. Think banks, healthcare portals, government websites, or any company storing private data online. If your web app uses this specific tool to protect login pages or private documents, you're potentially at risk.
The good news: security researchers haven't spotted anyone actively exploiting this in the wild yet. The bad news: hackers love vulnerabilities like this because they're relatively straightforward to abuse once they know about them.
Here's what to do. First, check if your organization uses Spring Security version 7.0 — your IT team can answer this immediately. Second, ask them to upgrade to version 7.0.5 or later, which patches the problem. Third, if your company maintains any websites, flag this to your tech team right now rather than waiting for something to go wrong.
Want the full technical analysis? Click "Technical" above.
CVE-2026-22754 is an authorization bypass in Spring Security affecting versions 7.0.0 through 7.0.4. When an application configures access control using the XML Security namespace tag <sec:intercept-url servlet-path="/servlet-path" pattern="/endpoint/**"/>, the framework silently drops the servlet-path prefix during path matcher computation. The resulting matcher evaluates only the pattern segment, never anchoring it to the intended servlet context. Any request whose URI suffix matches the pattern is then checked against the rule — but requests routed through a different servlet path that also match the pattern suffix receive no authorization check at all, because the rule was effectively computed against the wrong path space.
CVSS 7.5 (HIGH) — Network-reachable, no authentication required, no user interaction. The impact is a complete bypass of endpoint-level authorization gates.
Root cause:ServletPathRequestMatcher construction in the XML namespace parser strips the servlet-path attribute before passing the combined path to the underlying AntPathRequestMatcher, causing authorization rules scoped to a specific servlet path to silently become no-ops for paths that reach the protected handler through any other routing.
Affected Component
The defect lives in the Spring Security XML Security namespace configuration pipeline, specifically in the processing of <intercept-url> elements that carry a servlet-path attribute. The following subsystems are involved:
org.springframework.security.config.http.HttpConfigurationBuilder — parses <http> block children
org.springframework.security.web.util.matcher.AntPathRequestMatcher — the path matching engine
org.springframework.security.web.util.matcher.ServletPathRequestMatcher — wrapper that should strip the servlet path before delegating
org.springframework.security.web.access.intercept.FilterSecurityInterceptor — the enforcement point that consults the computed matchers
The XML namespace parser builds a RequestMatcher for each <intercept-url> element. When servlet-path is present, the intent is to scope the pattern to requests arriving under that servlet mount point. The parser should produce a ServletPathRequestMatcher that (a) verifies the incoming request's servlet path matches, and (b) strips that prefix before running the AntPathRequestMatcher against the remainder. Instead, the parser hands the raw pattern attribute — without the servlet path prefix — directly to AntPathRequestMatcher, then wraps the result in a ServletPathRequestMatcher that never gets invoked for the strip step. The rule ends up anchored to nothing meaningful.
/*
* Pseudocode reconstruction of HttpConfigurationBuilder#createRequestMatcher()
* Spring Security 7.0.4 (vulnerable)
*
* Called once per element during ApplicationContext refresh.
*/
RequestMatcher createRequestMatcher(Element interceptUrlElement) {
String pattern = interceptUrlElement.getAttribute("pattern"); // e.g. "/endpoint/**"
String servletPth = interceptUrlElement.getAttribute("servlet-path"); // e.g. "/servlet-path"
String method = interceptUrlElement.getAttribute("method");
if (servletPth != null && !servletPth.isEmpty()) {
/*
* BUG: pattern is passed WITHOUT prepending servletPth.
* The AntPathRequestMatcher therefore matches "/endpoint/**" in
* absolute URI space, not "/servlet-path/endpoint/**".
* ServletPathRequestMatcher wraps it but the inner matcher already
* evaluated against the wrong (full) path — the servlet-path gate
* is structurally present but guards the wrong path space.
*/
AntPathRequestMatcher inner = new AntPathRequestMatcher(
pattern, // BUG: should be servletPth + pattern
method
);
return new ServletPathRequestMatcher(inner, servletPth);
// ServletPathRequestMatcher.matches() strips servletPth from the
// request URI *before* calling inner.matches() — but inner was
// already built without the prefix, so every absolute-path request
// that matches `pattern` alone will hit this rule regardless of
// whether it came through `servletPth`.
}
return new AntPathRequestMatcher(pattern, method);
}
The consequence: two logically separate authorization rules — one for /servlet-path/endpoint/** and one for /other-servlet/endpoint/** — collapse into a single ambiguous rule that may fire for the wrong requests or, more critically, may not fire at all for the intended ones when the matcher chain is evaluated.
/*
* ServletPathRequestMatcher.matches() — intended behaviour vs. actual
*/
boolean matches(HttpServletRequest request) {
// Step 1: check servlet path
if (!request.getServletPath().equals(this.servletPath)) {
return false; // correct guard — but only reached if rule fires at all
}
// Step 2: strip servlet path and delegate
// BUG: inner AntPathRequestMatcher was built with the un-prefixed pattern,
// so a request to /servlet-path/endpoint/admin arrives here with
// pathInfo = "/endpoint/admin" — inner.matches() compares against
// "/endpoint/**" which IS correct at this step.
//
// The actual failure mode: rules defined for /servlet-path/endpoint/**
// ALSO match requests that bypass the servlet entirely (forward dispatches,
// include dispatches, or alternate servlet mappings) because the inner
// matcher is registered in the global filter chain without the prefix,
// making the authorisation decision non-deterministic across dispatch types.
return inner.matches(stripServletPath(request));
}
Exploitation Mechanics
EXPLOIT CHAIN — CVE-2026-22754 Authorization Bypass:
Target configuration (victim application's security.xml):
Expected behaviour:
GET /api/admin/users -> requires ROLE_ADMIN
GET /other/admin/users -> no matching rule (falls through to permitAll)
Actual behaviour (vulnerable):
The AntPathRequestMatcher for the ADMIN rule is built as "/admin/**" (no prefix).
The global filter chain registers this matcher in absolute-path space.
Step 1: Attacker identifies the application uses Spring Security 7.0.0-7.0.4
and the XML namespace config (check /actuator/beans or error pages).
Step 2: Attacker crafts a request that reaches the protected handler
through a path the broken matcher incorrectly evaluates:
GET /api/admin/users HTTP/1.1
Host: target.example.com
The SecurityFilterChain evaluates matchers in order.
The ADMIN rule's inner AntPathRequestMatcher sees path "/admin/users"
(after servlet-path strip) and matches — BUT only if the
ServletPathRequestMatcher's servlet-path check passes.
Step 3: Attacker exploits the alternate dispatch path:
— If the app uses a DispatcherServlet mapped to "/*" AND a separate
servlet mapped to "/api/*", requests forwarded internally skip
the outer SecurityFilterChain servlet-path check.
Forward dispatch (triggered via crafted request to exposed forward endpoint):
RequestDispatcher rd = request.getRequestDispatcher("/admin/users");
rd.forward(request, response);
The forwarded request hits the filter chain with:
servletPath = "" (or the forwarding servlet's path)
pathInfo = "/admin/users"
The broken AntPathRequestMatcher("/admin/**") matches "/admin/users".
The ServletPathRequestMatcher check for "/api" FAILS (servletPath != "/api").
Rule is skipped. Falls through to permitAll. Access granted.
Step 4: Attacker receives 200 OK with privileged response body.
Zero credentials required.
Impact: Full read/write access to any endpoint protected solely by
servlet-path-scoped intercept-url rules.
Memory Layout
This is a logic/authorization bug rather than a memory corruption vulnerability; there is no heap or stack corruption. The "memory" of interest is the Spring Security filter chain's internal matcher registry, which determines what rules are evaluated per request.
The fix in Spring Security 7.0.5 corrects the pattern passed to the inner AntPathRequestMatcher so that it includes the servlet path prefix, ensuring the inner matcher is anchored to the correct absolute path before the ServletPathRequestMatcher performs its strip-and-delegate operation.
// BEFORE (vulnerable — Spring Security 7.0.0 through 7.0.4):
RequestMatcher createRequestMatcher(Element interceptUrlElement) {
String pattern = interceptUrlElement.getAttribute("pattern");
String servletPath = interceptUrlElement.getAttribute("servlet-path");
String method = interceptUrlElement.getAttribute("method");
if (servletPath != null && !servletPath.isEmpty()) {
AntPathRequestMatcher inner = new AntPathRequestMatcher(
pattern, // BUG: missing servletPath prefix
method
);
return new ServletPathRequestMatcher(inner, servletPath);
}
return new AntPathRequestMatcher(pattern, method);
}
// AFTER (patched — Spring Security 7.0.5):
RequestMatcher createRequestMatcher(Element interceptUrlElement) {
String pattern = interceptUrlElement.getAttribute("pattern");
String servletPath = interceptUrlElement.getAttribute("servlet-path");
String method = interceptUrlElement.getAttribute("method");
if (servletPath != null && !servletPath.isEmpty()) {
// FIX: inner matcher receives the fully-qualified path so that
// strip-and-delegate in ServletPathRequestMatcher operates on
// the correct residual path segment.
String fullPattern = servletPath + pattern; // e.g. "/api" + "/admin/**"
AntPathRequestMatcher inner = new AntPathRequestMatcher(
fullPattern, // FIXED: "/api/admin/**"
method
);
return new ServletPathRequestMatcher(inner, servletPath);
}
return new AntPathRequestMatcher(pattern, method);
}
Additionally, the patch adds a dedicated integration test asserting that a request dispatched via FORWARD to a path matching the pattern but not the servlet path is correctly denied, and that a direct request to /servlet-path/endpoint/** without the required role returns 403 Forbidden.
Detection and Indicators
Applications are vulnerable if all of the following are true:
Spring Security version is 7.0.0 – 7.0.4 (check pom.xml, build.gradle, or /actuator/info)
Security configuration uses XML namespace (<sec:http> / <http>) rather than Java DSL
One or more <intercept-url> elements carry the servlet-path attribute
Detection via grep on the classpath configuration:
# Find vulnerable intercept-url declarations in XML config files:
grep -rn 'intercept-url' src/main/resources/ \
| grep 'servlet-path'
# Check Spring Security version in Maven effective POM:
mvn dependency:tree | grep 'spring-security-config'
# Actuator endpoint exposure check (if management endpoints are open):
curl -s http://target/actuator/beans \
| python3 -m json.tool \
| grep -A3 'FilterSecurityInterceptor'
Access log indicators: look for 200 responses to admin-pattern URIs from unauthenticated sessions, particularly from requests where the X-Forwarded-* headers or dispatcher type indicate internal forwarding.
Remediation
Primary: Upgrade to Spring Security 7.0.5 or later. This is a drop-in patch release with no API changes.
Workaround (if immediate upgrade is not possible): Migrate servlet-path-scoped rules from XML namespace to the Java DSL, which correctly computes the full path matcher:
// Java DSL equivalent — not affected by CVE-2026-22754:
http.authorizeHttpRequests(auth -> auth
.requestMatchers(new AntPathRequestMatcher("/api/admin/**", null))
.hasRole("ADMIN")
.anyRequest().permitAll()
);
// Note: manually prefix the servlet path in the pattern string.
// The Java DSL does not support the servlet-path shorthand attribute.
Defense in depth: Enable Spring Security's DispatcherType filter registration to ensure the security filter chain is applied to FORWARD and INCLUDE dispatches, not only REQUEST:
// Force security filter to apply to all dispatch types (Spring Boot):
@Bean
public FilterRegistrationBean securityFilterChain() {
FilterRegistrationBean<...> reg = new FilterRegistrationBean<>(...);
reg.setDispatcherTypes(EnumSet.allOf(DispatcherType.class)); // REQUEST, FORWARD, INCLUDE, ERROR, ASYNC
return reg;
}
Without this, even a correctly patched matcher registry can be bypassed via internal dispatch in complex servlet container configurations. Both mitigations together eliminate the attack surface described in this advisory.