Fix SSRF via jwks_uri#4000
Conversation
Problem: UAA allows operators and clients to configure a jwks_uri pointing to an external JWKS endpoint (used for private_key_jwt client authentication). There was no enforcement preventing a jwks_uri from resolving to internal/private network addresses, making UAA a vector for Server-Side Request Forgery (SSRF) and DNS rebinding attacks — an attacker could point the URI at cloud instance-metadata endpoints (e.g. 169.254.169.254), internal services, or loopback addresses. Fix — two layers of defense: 1. Validation-time check (PrivateNetworkGuard): When a client registers or updates a jwks_uri, ClientJwtConfiguration now resolves the hostname at validation time and rejects any URI whose host resolves to a private, loopback, link-local, multicast, or cloud-metadata address (including 169.254.169.254). Unresolvable hosts are allowed through at validation time — the fetch will simply fail later. 2. Fetch-time check (PrivateNetworkBlockingDnsResolver + safeRestTemplate): A new PrivateNetworkBlockingDnsResolver wraps the system DNS resolver and blocks private addresses at connection time. UaaHttpRequestUtils.createSafeRequestFactory builds a dedicated RestTemplate using this resolver. OidcMetadataFetcher uses this safeRestTemplate when fetching JWKS content for client JWT authentication, providing defense-in-depth against DNS rebinding (where a hostname resolves to a public IP at validation time but rebinds to a private IP at fetch time).
8f14c1a to
4c91546
Compare
There was a problem hiding this comment.
Pull request overview
Introduces defense-in-depth protections against SSRF / DNS rebinding via operator- or client-supplied jwks_uri (used for private_key_jwt) by adding validation-time private-network checks and fetch-time DNS blocking in the HTTP client used to retrieve JWKS content.
Changes:
- Add
PrivateNetworkGuard+ tests to detect/block private/loopback/link-local/multicast/metadata IP targets duringjwks_urivalidation. - Add
PrivateNetworkBlockingDnsResolverand wire a newsafeRestTemplate(with a DNS-blocking request factory) intoOidcMetadataFetcherfor JWKS fetching. - Update tests and bootstrap configuration paths to accommodate the new behavior.
Reviewed changes
Copilot reviewed 10 out of 10 changed files in this pull request and generated 4 comments.
Show a summary per file
| File | Description |
|---|---|
| server/src/main/java/org/cloudfoundry/identity/uaa/client/ClientJwtConfiguration.java | Adds validation-time resolution and blocking for jwks_uri (HTTPS path) via PrivateNetworkGuard. |
| server/src/main/java/org/cloudfoundry/identity/uaa/provider/oauth/OidcMetadataFetcher.java | Uses a new injected safeRestTemplate when fetching JWKS for ClientJwtConfiguration. |
| server/src/main/java/org/cloudfoundry/identity/uaa/util/UaaHttpRequestUtils.java | Adds createSafeRequestFactory using a DNS resolver that blocks private addresses. |
| server/src/main/java/org/cloudfoundry/identity/uaa/util/PrivateNetworkGuard.java | New utility implementing “blocked address” detection and assertPublic(URI) resolution logic. |
| server/src/main/java/org/cloudfoundry/identity/uaa/util/PrivateNetworkBlockingDnsResolver.java | New HttpClient5 DnsResolver wrapper that rejects blocked/private addresses at resolve-time. |
| server/src/main/java/org/cloudfoundry/identity/uaa/impl/config/RestTemplateConfig.java | Adds a Spring @Bean for safeRestTemplate. |
| server/src/main/java/org/cloudfoundry/identity/uaa/SpringServletXmlBeansConfiguration.java | Injects safeRestTemplate into the OidcMetadataFetcher bean. |
| server/src/test/java/org/cloudfoundry/identity/uaa/util/PrivateNetworkGuardTest.java | New unit tests covering blocked vs public IPs and URI host handling. |
| server/src/test/java/org/cloudfoundry/identity/uaa/client/ClientJwtConfigurationTest.java | Adds a regression test ensuring private IP jwks_uri values are rejected. |
| server/src/test/java/org/cloudfoundry/identity/uaa/client/ClientAdminBootstrapTests.java | Updates bootstrap test JWKS URI example value. |
Comments suppressed due to low confidence (1)
server/src/main/java/org/cloudfoundry/identity/uaa/client/ClientJwtConfiguration.java:256
_cannot be used as an identifier in Java 25+. This catch parameter will not compile; use a named variable (or omit the binding by catching and ignoring with a named variable).
} catch (java.net.UnknownHostException _) {
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 10 out of 10 changed files in this pull request and generated 3 comments.
Comments suppressed due to low confidence (3)
server/src/main/java/org/cloudfoundry/identity/uaa/client/ClientJwtConfiguration.java:247
validateJwksUristill allowshttp://localhostand skips the private-network guard for HTTP. Since JWKS fetching is now done via thesafeRestTemplate(which blocks loopback/private addresses at connection time), this creates a configuration that can pass validation but will fail at runtime, and it also contradicts the PR description’s goal of rejecting loopback/private targets at validation time.
Consider requiring HTTPS and applying PrivateNetworkGuard.assertPublic(...) for all allowed URIs so misconfigurations fail fast.
if (!"https".equals(validateJwksUri.getScheme()) && !"http".equals(validateJwksUri.getScheme())) {
throw new InvalidClientDetailsException("Invalid private_key_jwt: jwks_uri must be either using https or http");
}
if ("http".equals(validateJwksUri.getScheme()) && !"localhost".equals(validateJwksUri.getHost())) {
throw new InvalidClientDetailsException("Invalid private_key_jwt: jwks_uri with http is not on localhost");
server/src/test/java/org/cloudfoundry/identity/uaa/client/ClientAdminBootstrapTests.java:262
- This test configures
jwks_uriashttp://localhost..., which becomes invalid (or at least non-functional) once jwks_uri is restricted to public HTTPS endpoints to prevent SSRF/DNS rebinding. Using a non-local HTTPS URL here keeps the test focused on bootstrap persistence without relying on loopback exceptions.
map.put("jwks_uri", "http://localhost:8080/uaa");
server/src/main/java/org/cloudfoundry/identity/uaa/impl/config/RestTemplateConfig.java:42
safeRestTemplate()is built usingcreateSafeRequestFactory(timeout), which hardcodes the default pool/keep-alive/retry settings rather than using the configurablerest.template.*properties already present in this config class. If operators tunemaxTotal,maxPerRoute,maxKeepAlive, etc., those settings won’t apply to JWKS fetches (the new security-critical path).
@Bean
public RestTemplate safeRestTemplate() {
return new RestTemplate(UaaHttpRequestUtils.createSafeRequestFactory(timeout));
}
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 14 out of 14 changed files in this pull request and generated 6 comments.
Comments suppressed due to low confidence (2)
server/src/main/java/org/cloudfoundry/identity/uaa/util/UaaHttpRequestUtils.java:108
- createSafeRequestFactory() disables redirect handling entirely. This is a behavioral change from the other RestTemplates (which use DefaultRedirectStrategy) and will cause jwks_uri fetches to fail if an endpoint relies on HTTP redirects. If redirects must be supported, consider a redirect strategy that re-validates each redirect target (scheme + PrivateNetworkGuard + DNS blocking) rather than disabling redirects globally.
HttpClientBuilder builder = HttpClients.custom()
.useSystemProperties()
.setUserTokenHandler(NoopUserTokenHandler.INSTANCE)
.disableRedirectHandling();
PoolingHttpClientConnectionManager cm = PoolingHttpClientConnectionManagerBuilder.create()
uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/ClientAdminEndpointsIntegrationTests.java:665
- This integration test now uses an external hostname for jwks_uri, which will trigger DNS lookups during validation and can make tests sensitive to DNS/network availability. Prefer a public IP literal to avoid DNS dependencies.
ClientJwtChangeRequest def = new ClientJwtChangeRequest(null, null, null);
def.setJsonWebKeyUri("https://login.example.com/token_keys");
def.setClientId("admin");
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 14 out of 14 changed files in this pull request and generated no new comments.
Comments suppressed due to low confidence (3)
server/src/main/java/org/cloudfoundry/identity/uaa/provider/oauth/OidcMetadataFetcher.java:88
jwks_urifetches tolocalhostusenonTrustingRestTemplate, which is configured withDefaultRedirectStrategy(redirects enabled). This means alocalhostJWKS endpoint could redirect to a private IP literal and the request would follow it, undermining the “redirects disabled” SSRF defense used bysafeRestTemplate. Consider using a dedicated RestTemplate for localhost fetches with redirects disabled (but without private-network DNS blocking), or implement redirect validation/blocking for this code path.
String jwksUri = clientJwtConfiguration.getJwksUri();
RestTemplate template = isLocalhost(jwksUri) ? nonTrustingRestTemplate : safeRestTemplate;
byte[] rawContents = getJsonBody(jwksUri, false, true, null, template);
uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/ClientAdminEndpointsIntegrationTests.java:664
- This test uses an external hostname (
login.example.com) which will trigger DNS resolution duringjwks_urivalidation. In environments with restricted or slow DNS, this can make tests flaky or slow; using a public IP literal keeps the test deterministic while still exercising the HTTPS path.
def.setJsonWebKeyUri("https://login.example.com/token_keys");
server/src/test/java/org/cloudfoundry/identity/uaa/client/ClientJwtConfigurationTest.java:33
- This unit test now triggers DNS resolution during
jwks_urivalidation (HTTPS paths callInetAddress.getAllByName). Using a public IP literal instead avoids dependence on external DNS and prevents slow/flaky runs in restricted CI environments.
assertThat(ClientJwtConfiguration.parse("https://any.domain.net/openid/jwks-uri")).isNotNull();
Problem: UAA allows operators and clients to configure a jwks_uri pointing to an external JWKS endpoint (used for private_key_jwt client authentication). There was no enforcement preventing a jwks_uri from resolving to internal/private network addresses, making UAA a vector for Server-Side Request Forgery (SSRF) and DNS rebinding
attacks — an attacker could point the URI at cloud instance-metadata endpoints (e.g. 169.254.169.254), internal services, or loopback addresses.
Fix — two layers of defense: