Skip to content

fix(#5452): add optional ssrf check mechanism#5464

Open
SteKoe wants to merge 5 commits into
masterfrom
chore/5452-ssrf-check
Open

fix(#5452): add optional ssrf check mechanism#5464
SteKoe wants to merge 5 commits into
masterfrom
chore/5452-ssrf-check

Conversation

@SteKoe

@SteKoe SteKoe commented Jun 19, 2026

Copy link
Copy Markdown
Contributor

This pull request introduces opt-in protection against Server-Side Request Forgery (SSRF) attacks in Spring Boot Admin Server, focusing on validating and restricting instance registration URLs and proxy targets. It adds configuration options for enabling SSRF protection, customizing allowed IP ranges and schemes, and provides extension points for advanced use cases. Documentation is updated to explain the risks, configuration, and best practices.

SSRF Protection Feature:

  • Added new SSRF protection mechanism to validate healthUrl, managementUrl, and serviceUrl during instance registration and proxying, blocking requests to private/internal addresses and non-HTTP(S) schemes. This is disabled by default and can be enabled via configuration. [1] [2]
  • Introduced configuration properties in AdminServerProperties (ssrf-protection.enabled, allowed-cidrs, allowed-schemes) and supporting beans (InetAddressFilter, SsrfUrlValidator) for flexible address and scheme allowlisting. [1] [2] [3]
  • Updated InstanceRegistry and both servlet and reactive InstancesProxyController to enforce SSRF validation at registration and proxy time. [1] [2] [3]

Documentation Updates:

  • Added a comprehensive SSRF protection guide (40-ssrf-protection.md) covering risks, configuration, extension, and integration with other security features.
  • Updated security index, navigation, and checklist to include SSRF protection as a recommended step for securing deployments. [1] [2] [3] [4]

General/Other:

  • Copyright years updated to 2026 in affected files. [1] [2] [3] [4]

These changes significantly improve the security posture of Spring Boot Admin Server by providing robust, configurable defenses against SSRF attacks.

closes #5452

@SteKoe SteKoe requested a review from a team as a code owner June 19, 2026 08:43
@SteKoe SteKoe force-pushed the chore/5452-ssrf-check branch 2 times, most recently from 0e995c5 to 24010f7 Compare June 19, 2026 08:45
Comment thread spring-boot-admin-docs/src/site/docs/05-security/40-ssrf-protection.md Outdated
Comment thread spring-boot-admin-docs/src/site/docs/05-security/40-ssrf-protection.md Outdated
SteKoe added 5 commits July 5, 2026 11:39
- Reject URISyntaxException in SsrfUrlValidator instead of silently
  accepting; a malformed URL is a potential bypass vector
- Use @ConditionalOnMissingBean(name=) on ssrfInetAddressFilter to
  prevent collision with any user-defined InetAddressFilter bean
- Add clarifying comment in InstanceWebProxy explaining why relative
  URIs are skipped (path-only, no host to validate)
- Remove unused InetAddressFilter imports from InstanceWebProxy and
  reactive InstancesProxyController
- Fix space-indentation and blank-line formatting in servlet
  InstancesProxyController (checkstyle / spring-javaformat)
- Expand allowed-cidrs examples in docs to show single host, subnet,
  whole RFC 1918 classes, and IPv6 as separate copy-paste snippets
- Add test: rejects_malformedUrl covers the URISyntaxException path
- Reject URLs where URI.getHost() is null but URI.getAuthority() is
  non-null: this covers octal (0251.0376.0251.0376) and Unicode digit
  hosts that java.net.URI cannot parse into a standard hostname. Without
  this guard, octal-encoded private IPs pass validation entirely.
- Use authority != null instead of StringUtils.hasText() — getAuthority()
  is never an empty string, so the plain null-check is exact and clearer.
- Add BypassAttempts test class covering: decimal-encoded IPs (resolved
  correctly by InetAddress), octal and Unicode (caught by the new guard),
  and uppercase scheme normalisation (SBA's own toLowerCase logic).
  Tests that only exercise JDK URI parsing or Spring Boot's InetAddressFilter
  internals were not added — those are tested by their respective owners.
@SteKoe SteKoe force-pushed the chore/5452-ssrf-check branch from 1b68118 to d15448b Compare July 5, 2026 09:39
uri = new URI(url);
}
catch (URISyntaxException ex) {
throw new SsrfProtectionException(

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here, and as a general thing, I would not throw away ex but I would pass it down as cause parameter (to be added) in SsrfProtectionException so that consumers of SBA have, if needed, the full stacktrace available for analysis purposes.


@Bean
@ConditionalOnMissingBean
@ConditionalOnMissingBean(name = "ssrfInetAddressFilter")

@cdprete cdprete Jul 5, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you please define this name in a publicly exposed constant?

In this way, if there is the need to redefine this bean for whatever reason, the constant can be referenced instead of having to know the exact name.

This would provide also more safety across SBA releases in the case the name gets changed since the constant would reflect that automatically without breaking the redefinition said above.

Please be aware that this will then backoff if another bean with the same name is defined without taking into account its type.

// ForwardRequest.uri is a relative path built by the proxy controllers (no
// host). The actual host is supplied by InstanceWebClient when it resolves
// the instance's management URL. Absolute URIs (if ever present) are
// validated; relative ones have no host to check.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't be a responsibility of the validator to decide this?

I would just pass the URI in there and let the validator do something like if(!uri.isAbsolute()) return;.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

SSRF via unauthenticated instance registration with arbitrary managementUrl

2 participants