Spring Cloud Config Profile Substitution Can Allow Unintended Access To Files And Enable SSRF Attacks
Description
Vulnerability in Spring Cloud when substituting the profile parameter from a request made to the Spring Cloud Config Server configured to the native file system as a backend, because it was possible to access files outside of the configured search directories.This issue affects Spring Cloud: from 3.1.X before 3.1.13, from 4.1.X before 4.1.9, from 4.2.X before 4.2.3, from 4.3.X before 4.3.2, from 5.0.X before 5.0.2.
AI Insight
LLM-synthesized narrative grounded in this CVE's description and references.
Spring Cloud Config Server with native backend is vulnerable to path traversal via crafted profile parameters, allowing access to files outside configured search directories.
Vulnerability
Overview
CVE-2026-22739 is a path traversal vulnerability in Spring Cloud Config Server when configured with the native file system as a backend. The flaw resides in how the server handles the profile parameter from incoming requests. By substituting a profile value with directory traversal sequences (e.g., .. or URL-encoded %2e%2e), an attacker can cause the server to access files outside of the intended search directories [2][3]. The official description confirms that the server does not properly validate the profile parameter before using it to locate configuration resources [1].
Exploitation
The attack surface is the HTTP API of the Spring Cloud Config Server. An attacker can exploit this vulnerability by sending a crafted request containing malicious profile parameters such as bar,..,foo or simply ... The commit that fixes the vulnerability includes tests demonstrating that these inputs now result in an InvalidEnvironmentRequestException [3]. No authentication is mentioned as a requirement in the references, indicating that the vulnerable endpoints may be publicly exposed or accessible to any network actor able to reach the server.
Impact
Successful exploitation allows an attacker to read arbitrary files from the server's filesystem, potentially including sensitive configuration data, credentials, or other secrets stored beyond the configured search paths. This could lead to information disclosure and further compromise of the Spring Cloud environment. The vulnerability affects multiple versions: 3.1.x before 3.1.13, 4.1.x before 4.1.9, 4.2.x before 4.2.3, 4.3.x before 4.3.2, and 5.0.x before 5.0.2 [2].
Mitigation
Spring Cloud has released patches in versions 5.0.2 and the respective fixes for older branches [4]. The fix introduces input validation that throws an InvalidEnvironmentRequestException for invalid profile parameters containing traversal sequences [3]. Users are strongly advised to upgrade to the latest secure version for their branch. No workarounds are mentioned in the provided references.
AI Insight generated on May 18, 2026. Synthesized from this CVE's description and the cited reference URLs; citations are validated against the source bundle.
Affected packages
Versions sourced from the GitHub Security Advisory.
| Package | Affected versions | Patched versions |
|---|---|---|
org.springframework.cloud:spring-cloud-config-serverMaven | >= 4.3.0, < 4.3.2 | 4.3.2 |
org.springframework.cloud:spring-cloud-config-serverMaven | >= 5.0.0-M1, < 5.0.2 | 5.0.2 |
org.springframework.cloud:spring-cloud-config-serverMaven | >= 4.2.0, <= 4.2.4 | — |
org.springframework.cloud:spring-cloud-config-serverMaven | >= 4.0.0, <= 4.1.7 | — |
org.springframework.cloud:spring-cloud-config-serverMaven | <= 3.1.10 | — |
Affected products
2- Range: <5.0.2,<4.3.2,<4.2.3,<4.1.9,<3.1.13
- Spring/Spring Cloudv5Range: 3.1.x
Patches
11870f07befd5Protect against invalid use of profiles
6 files changed · +74 −2
spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/environment/EnvironmentController.java+5 −2 modified@@ -36,7 +36,6 @@ import org.springframework.cloud.config.environment.Environment; import org.springframework.cloud.config.environment.EnvironmentMediaType; import org.springframework.cloud.config.environment.PropertySource; -import org.springframework.cloud.config.server.support.PathUtils; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; @@ -51,6 +50,7 @@ import static org.springframework.cloud.config.server.support.EnvironmentPropertySource.prepareEnvironment; import static org.springframework.cloud.config.server.support.EnvironmentPropertySource.resolvePlaceholders; +import static org.springframework.cloud.config.server.support.PathUtils.isInvalidEncodedLocation; /** * @author Dave Syer @@ -131,6 +131,9 @@ public Environment getEnvironment(String name, String profiles, String label, bo try { name = normalize(name); label = normalize(label); + if (isInvalidEncodedLocation(profiles)) { + throw new InvalidEnvironmentRequestException("Invalid request"); + } Environment environment = this.repository.findOne(name, profiles, label, includeOrigin); if (!this.acceptEmpty && (environment == null || environment.getPropertySources().isEmpty())) { throw new EnvironmentNotFoundException("Profile Not found"); @@ -145,7 +148,7 @@ public Environment getEnvironment(String name, String profiles, String label, bo } private String normalize(String part) { - if (PathUtils.isInvalidEncodedLocation(part)) { + if (isInvalidEncodedLocation(part)) { throw new InvalidEnvironmentRequestException("Invalid request"); } return Environment.normalize(part);
spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/resource/ResourceController.java+8 −0 modified@@ -31,6 +31,7 @@ import org.springframework.cloud.config.environment.Environment; import org.springframework.cloud.config.server.encryption.ResourceEncryptor; import org.springframework.cloud.config.server.environment.EnvironmentRepository; +import org.springframework.cloud.config.server.environment.InvalidEnvironmentRequestException; import org.springframework.core.io.Resource; import org.springframework.http.HttpHeaders; import org.springframework.http.MediaType; @@ -48,6 +49,7 @@ import static org.springframework.cloud.config.server.support.EnvironmentPropertySource.prepareEnvironment; import static org.springframework.cloud.config.server.support.EnvironmentPropertySource.resolvePlaceholders; +import static org.springframework.cloud.config.server.support.PathUtils.isInvalidEncodedLocation; /** * An HTTP endpoint for serving up templated plain text resources from an underlying @@ -142,6 +144,9 @@ synchronized String retrieve(ServletWebRequest request, String name, String prof boolean resolvePlaceholders, String acceptedCharset) throws IOException { name = Environment.normalize(name); label = Environment.normalize(label); + if (isInvalidEncodedLocation(profile)) { + throw new InvalidEnvironmentRequestException("Invalid request"); + } Resource resource = this.resourceRepository.findOne(name, profile, label, path); if (checkNotModified(request, resource)) { // Content was not modified. Just return. @@ -213,6 +218,9 @@ private synchronized byte[] binary(ServletWebRequest request, String name, Strin String path) throws IOException { name = Environment.normalize(name); label = Environment.normalize(label); + if (isInvalidEncodedLocation(profile)) { + throw new InvalidEnvironmentRequestException("Invalid request"); + } Resource resource = this.resourceRepository.findOne(name, profile, label, path); if (checkNotModified(request, resource)) { // Content was not modified. Just return.
spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/support/AbstractScmAccessor.java+14 −0 modified@@ -38,6 +38,8 @@ import org.springframework.core.io.support.PathMatchingResourcePatternResolver; import org.springframework.util.FileSystemUtils; import org.springframework.util.StringUtils; +import org.springframework.web.util.UriComponents; +import org.springframework.web.util.UriComponentsBuilder; /** * Base class for components that want to access a source control management system. @@ -150,9 +152,21 @@ public void setUri(String uri) { // If there's no context path add one uri = uri + "/"; } + validateNoTemplateInAuthority(uri); this.uri = uri; } + private void validateNoTemplateInAuthority(String urlTemplate) { + UriComponents components = UriComponentsBuilder.fromUriString(urlTemplate).build(); + // If the port is templated this call will throw an Exception + components.getPort(); + String host = components.getHost(); + if (host != null && (host.contains("{") || host.contains("}"))) { + throw new IllegalArgumentException("Template placeholders not allowed in host: " + urlTemplate); + } + + } + public File getBasedir() { return this.basedir; }
spring-cloud-config-server/src/test/java/org/springframework/cloud/config/server/environment/EnvironmentControllerTests.java+12 −0 modified@@ -554,6 +554,18 @@ public void nameWithPoundEncoded() { .isInstanceOf(InvalidEnvironmentRequestException.class); } + @Test + public void invalidProfileTests() { + assertThatThrownBy(() -> this.controller.labelled("application", "bar,..,foo", "label")) + .isInstanceOf(InvalidEnvironmentRequestException.class); + assertThatThrownBy(() -> this.controller.labelled("application", "..", "label")) + .isInstanceOf(InvalidEnvironmentRequestException.class); + assertThatThrownBy(() -> this.controller.labelled("application", "%2e%2e", "label")) + .isInstanceOf(InvalidEnvironmentRequestException.class); + assertThatThrownBy(() -> this.controller.labelled("application", "bar,%2e%2e,foo", "label")) + .isInstanceOf(InvalidEnvironmentRequestException.class); + } + abstract class MockMvcTestCases { protected MockMvc mvc;
spring-cloud-config-server/src/test/java/org/springframework/cloud/config/server/environment/MultipleJGitEnvironmentApplicationPlaceholderRepositoryTests.java+10 −0 modified@@ -34,6 +34,7 @@ import org.springframework.core.env.StandardEnvironment; import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; /** * @author Dave Syer @@ -115,6 +116,15 @@ public void otherMappingRepo() { assertVersion(environment); } + @Test + void invalidAuthorityTests() { + assertThatThrownBy(() -> createRepository("test", "*-config-repo", "http://{profile}:8080/test1-config-repo")) + .isInstanceOf(IllegalArgumentException.class); + assertThatThrownBy( + () -> createRepository("test", "*-config-repo", "http://localhost:{profile}/test1-config-repo")) + .isInstanceOf(IllegalStateException.class); + } + @Test @Disabled("not supported yet (placeholders in search paths with lists)") public void profilesInSearchPaths() {
spring-cloud-config-server/src/test/java/org/springframework/cloud/config/server/resource/ResourceControllerTests.java+25 −0 modified@@ -28,6 +28,7 @@ import org.springframework.boot.WebApplicationType; import org.springframework.boot.builder.SpringApplicationBuilder; import org.springframework.cloud.config.server.encryption.ResourceEncryptor; +import org.springframework.cloud.config.server.environment.InvalidEnvironmentRequestException; import org.springframework.cloud.config.server.environment.NativeEnvironmentProperties; import org.springframework.cloud.config.server.environment.NativeEnvironmentRepository; import org.springframework.cloud.config.server.environment.NativeEnvironmentRepositoryTests; @@ -37,6 +38,7 @@ import org.springframework.web.context.request.ServletWebRequest; import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.Mockito.mock; @@ -368,4 +370,27 @@ public void setSearchLocationsAppendSlashByConstructor() { assertThat(repo.getSearchLocations()[0]).isEqualTo("classpath:/test/"); } + @Test + public void invalidProfileTests() { + assertThatThrownBy( + () -> this.controller.retrieve("application", "bar,..,foo", "label", "template.json", true, "UTF-8")) + .isInstanceOf(InvalidEnvironmentRequestException.class); + assertThatThrownBy(() -> this.controller.binary("application", "bar,..,foo", "label", "template.json")) + .isInstanceOf(InvalidEnvironmentRequestException.class); + assertThatThrownBy(() -> this.controller.retrieve("application", "..", "label", "template.json", true, "UTF-8")) + .isInstanceOf(InvalidEnvironmentRequestException.class); + assertThatThrownBy(() -> this.controller.binary("application", "..", "label", "template.json")) + .isInstanceOf(InvalidEnvironmentRequestException.class); + assertThatThrownBy( + () -> this.controller.retrieve("application", "%2e%2e", "label", "template.json", true, "UTF-8")) + .isInstanceOf(InvalidEnvironmentRequestException.class); + assertThatThrownBy(() -> this.controller.binary("application", "%2e%2e", "label", "template.json")) + .isInstanceOf(InvalidEnvironmentRequestException.class); + assertThatThrownBy(() -> this.controller.retrieve("application", "bar,%2e%2e,foo", "label", "template.json", + true, "UTF-8")) + .isInstanceOf(InvalidEnvironmentRequestException.class); + assertThatThrownBy(() -> this.controller.binary("application", "bar,%2e%2e,foo", "label", "template.json")) + .isInstanceOf(InvalidEnvironmentRequestException.class); + } + }
Vulnerability mechanics
Generated on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.
References
6- github.com/advisories/GHSA-3qwq-q9vm-5j42ghsaADVISORY
- nvd.nist.gov/vuln/detail/CVE-2026-22739ghsaADVISORY
- github.com/spring-cloud/spring-cloud-config/commit/1870f07befd5f62edcfdaea5ad82441d0fd49912ghsaWEB
- github.com/spring-cloud/spring-cloud-config/releases/tag/v4.3.2ghsaWEB
- github.com/spring-cloud/spring-cloud-config/releases/tag/v5.0.2ghsaWEB
- spring.io/security/cve-2026-22739ghsaWEB
News mentions
0No linked articles in our index yet.