Automated Threat Modeling

Spring Boot CVE-2026-40976: Quick Look at a Critical Actuator Authorization Bypass in Versions 4.0.0 Through 4.0.5

A short summary of CVE-2026-40976, a CVSS 9.1 authorization bypass in Spring Boot 4.0.0 through 4.0.5. Under one specific dependency configuration, it leaves every Actuator endpoint unauthenticated. Includes patch details and remediation guidance.

CVE Analysis

8 min read

ZeroPath CVE Analysis
ZeroPath CVE Analysis

2026-04-27

Spring Boot CVE-2026-40976: Quick Look at a Critical Actuator Authorization Bypass in Versions 4.0.0 Through 4.0.5
Experimental AI-Generated Content

This CVE analysis is an experimental publication that is completely AI-generated. The content may contain errors or inaccuracies and is subject to change as more information becomes available. We are continuously refining our process.

If you have feedback, questions, or notice any errors, please reach out to us.

[email protected]

Introduction

A misplaced conditional check in Spring Boot's default security auto-configuration silently disables all authorization rules for Actuator endpoints. Any unauthenticated user can then pull environment secrets, heap dumps, and configuration properties with a single HTTP request. The flaw, tracked as CVE-2026-40976 with a CVSS 3.1 score of 9.1, affects Spring Boot versions 4.0.0 through 4.0.5 and was patched in the 4.0.6 release on April 23, 2026.

The vulnerability is subtle because it only shows up under a specific but realistic dependency combination: a servlet-based web application that relies on the default security filter chain, includes spring-boot-actuator-autoconfigure, and does not include the newer spring-boot-health module. This configuration commonly appears in custom Boot starters, slimmed-down container images, and applications that deliberately exclude health endpoints to avoid conflicts.

Technical Information

Root Cause

CVE-2026-40976 is classified as CWE-862 (Missing Authorization). It comes from the interaction between Spring Boot 4.0's modular restructuring of dependencies and its auto-configured security defaults.

In Spring Boot 4.0, health endpoint support moved out of spring-boot-actuator-autoconfigure and into a dedicated spring-boot-health module. The default web security configuration in ManagementWebSecurityAutoConfiguration.java used a ClassUtils.isPresent(...) check to find out whether the HealthEndpoint class was on the classpath. The intent was to grant unauthenticated access (permitAll()) to the health endpoint only when it existed, and to lock down everything else with anyRequest().authenticated().

The flaw was in the placement of that check. In the vulnerable code, the whole authorizeHttpRequests block sat inside the isPresent guard:

if (ClassUtils.isPresent("org.springframework.boot.health.actuate.endpoint.HealthEndpoint", getClass().getClassLoader())) { http.authorizeHttpRequests((requests) -> { requests.requestMatchers(healthMatcher(), additionalHealthPathsMatcher()).permitAll(); requests.anyRequest().authenticated(); }); }

When spring-boot-health was not on the classpath (so the HealthEndpoint class was absent), Spring Boot silently skipped the entire authorization configuration. The default filter chain was registered with no authorization rules at all, and none covered the Actuator path, so every exposed endpoint was reachable anonymously.

Exploitation Conditions

An application is exploitable only when all of these conditions are true at once:

ConditionDescription
Application TypeMust be a servlet-based web application
Security ConfigurationMust have no Spring Security configuration of its own and rely entirely on the default web security filter chain
Actuator DependencyThe classpath must include spring-boot-actuator-autoconfigure
Health DependencyThe classpath must NOT include spring-boot-health

If any single condition is false, the application is not vulnerable.

Attack Flow

An attack on a vulnerable application is straightforward:

  1. Reconnaissance: The attacker identifies a Spring Boot application (often from response headers or error page formatting) and probes the /actuator base path. On a vulnerable instance, this returns a JSON document listing every available Actuator endpoint, with no authentication required.

  2. Secret Exfiltration via /actuator/env: A GET request to /actuator/env returns the full application Environment, including resolved property values such as database passwords, API keys, and JWT signing keys. One request is enough for full credential compromise.

  3. Heap Dump Capture via /actuator/heapdump: Requesting /actuator/heapdump triggers a full JVM heap dump. The binary file contains live object state, in-memory credentials, and active session tokens, and offline analysis can reveal secrets that never appear in environment properties.

  4. Log Manipulation via /actuator/loggers: The attacker can POST to /actuator/loggers to change logging levels at runtime. They can suppress security audit logs to cover their tracks, or turn on verbose logging to capture more of the sensitive data flowing through the application.

  5. Architecture Mapping via /actuator/configprops: /actuator/configprops returns every configuration property, which exposes the application's architecture, its integration wiring, and the downstream service endpoints the attacker can target next.

Impact Summary

EndpointExposed DataBusiness Impact
/actuator/envDatabase passwords, API keys, JWT signing keysFull credential compromise and secret exfiltration
/actuator/heapdumpLive JVM heap with in-memory credentials and session tokensDeep data exposure enabling offline analysis
/actuator/configpropsAll configuration propertiesReveals application architecture and integration details
/actuator/loggersLogger configuration (read/write)Attackers can suppress security logs or enable verbose capture

The environment and heap dump endpoints alone are typically enough to take an attacker from anonymous network access to full credential compromise in a single request.

Patch Information

The Spring team fixed CVE-2026-40976 in Spring Boot 4.0.6, released on April 23, 2026. Andy Wilkinson wrote the fix, committed as SHA 874f6294b91da18367b8b5ab7b2fad3fa23cfba6, and it closed GitHub issue #50188 directly. The fix was also forward-ported to the 4.1.x line through issue #50190 (milestone 4.1.0-RC1).

The Servlet Fix

The fix in ManagementWebSecurityAutoConfiguration.java changes the nesting. The authorization lambda now always applies, and only the health-specific permitAll() matcher is conditional:

- if (ClassUtils.isPresent("org.springframework.boot.health.actuate.endpoint.HealthEndpoint", - getClass().getClassLoader())) { - http.authorizeHttpRequests((requests) -> { - requests.requestMatchers(healthMatcher(), additionalHealthPathsMatcher()).permitAll(); - requests.anyRequest().authenticated(); - }); - } + http.authorizeHttpRequests((requests) -> { + if (ClassUtils.isPresent("org.springframework.boot.health.actuate.endpoint.HealthEndpoint", + getClass().getClassLoader())) { + requests.requestMatchers(healthMatcher(), additionalHealthPathsMatcher()).permitAll(); + } + requests.anyRequest().authenticated(); + });

The change looks simple, but it matters a great deal. With requests.anyRequest().authenticated() moved outside the isPresent check, every request requires authentication whether or not the health module is available.

The Reactive Fix

The WebFlux counterpart in ReactiveManagementWebSecurityAutoConfiguration.java had the opposite problem. It had no ClassUtils.isPresent guard at all, so it always tried to call healthMatcher() and additionalHealthPathsMatcher(). The fix wraps only the health-specific permit rules in the presence check and leaves anyExchange().authenticated() applying in every case:

http.authorizeExchange((exchanges) -> { - exchanges.matchers(healthMatcher(), additionalHealthPathsMatcher()).permitAll(); + if (ClassUtils.isPresent("org.springframework.boot.health.actuate.endpoint.HealthEndpoint", + getClass().getClassLoader())) { + exchanges.matchers(healthMatcher(), additionalHealthPathsMatcher()).permitAll(); + } exchanges.anyExchange().authenticated(); });

New Tests

The commit adds a dedicated test method, securesEverythingElseWhenHealthIsAbsent(), to both the servlet and reactive test suites. It uses @ClassPathExclusions(packages = "org.springframework.boot.health.actuate.endpoint") to reproduce the exact vulnerable configuration and confirms that all endpoints now return 401 Unauthorized. The existing tests were updated too, so that the HealthContributorAutoConfiguration, HealthContributorRegistryAutoConfiguration, and HealthEndpointAutoConfiguration classes are added explicitly, and only in tests that exercise health endpoint behavior.

The whole change is modest, 121 additions and 52 deletions across 4 files, but its security impact is large: it closes a full authorization bypass that left every endpoint unauthenticated.

Affected Systems and Versions

The vulnerability affects Spring Boot versions 4.0.0 through 4.0.5.

An application is vulnerable only if it meets all four of these conditions:

  1. It is a servlet-based web application.
  2. It has no Spring Security configuration of its own and relies entirely on the default web security filter chain.
  3. Its classpath includes spring-boot-actuator-autoconfigure.
  4. Its classpath does not include spring-boot-health.

Applications on Spring Boot versions before 4.0.0 are not affected, because the health endpoint only moved into a separate module in the 4.0 line. Spring Boot 4.0.6 or later, and 4.1.0-RC1 or later, contain the fix.

Vendor Security History

The Spring Boot 4.0.6 release that patches CVE-2026-40976 also fixes three other vulnerabilities found in the same version range:

CVE IDVulnerability
CVE-2026-40970Elasticsearch auto-configuration TLS hostname verification disablement
CVE-2026-40972DevTools remote secret comparison timing attacks
CVE-2026-40975Random value property source using weak PRNG
CVE-2026-40976Default security filter chain missing authorization rule

Four security fixes in a single release indicate an active security maintenance cycle. The Spring team disclosed the vulnerability and shipped the patch on the same day (April 23, 2026), which shows a coordinated disclosure and rapid response process.

References

Detect & fix
what others miss

Works with
  • GitHub
  • GitLab
  • Bitbucket
  • Azure DevOps Services
  • Gerrit
  • CVS
  • Jira
  • Linear
  • Slack
  • Security Compass
Explore integrations
Security magnifying glass visualization