Spring Boot 4.1 InetAddressFilter: Source-Level SSRF Protection Against Internal IPs & Cloud Metadata
This article dissects Spring Boot 4.1's built-in InetAddressFilter, showing how a single @Bean declaration blocks SSRF attacks by validating resolved IPs against private, loopback, link-local (including 169.254.169.254 cloud metadata), and multicast ranges before TCP connections are made, with automatic integration across RestClient, RestTemplate, and WebClient.
SSRF Attack Surface and Why Traditional Defenses Fail
Server-Side Request Forgery (SSRF) lets attackers force a backend server to make arbitrary HTTP requests. Because the server often resides in a trusted network, it can reach internal databases, caches, management consoles, and cloud instance metadata services. The 2019 Capital One breach — 100 million records stolen — originated from an SSRF vulnerability that accessed AWS metadata at
http://169.254.169.254/latest/meta-data/iam/security-credentials/to obtain temporary IAM credentials.
Common string-based blacklists (e.g., checking for 10., 192.168.) are easily bypassed:
DNS rebinding : attacker controls evil.com resolving first to a public IP (passes check), then to 127.0.0.1 (actual request hits localhost).
IP notation variants : 0x7f000001 (hex), 2130706433 (decimal), 0177.0.0.1 (octal) all represent 127.0.0.1.
IPv6 loopback : [::1].
Redirects : initial URL points to a benign domain; a 302 redirects to 169.254.169.254.
DNS rebinding (time-of-check vs time-of-use) : first resolution returns public IP, second returns private IP.
Correct defense requires: (1) resolve DNS first, then validate the actual IP; (2) re-validate on every redirect; (3) cover both IPv4 and IPv6; (4) include all special ranges — private, loopback, link-local, multicast, unspecified, reserved.
InetAddressFilter: Spring Boot 4.1's Built-In SSRF Guard
Spring Boot 4.1 introduces org.springframework.boot.http.client.InetAddressFilter, a functional interface:
@FunctionalInterface
public interface InetAddressFilter {
boolean check(InetAddress address); // true = allow, false = deny
static InetAddressFilter externalAddresses() { ... }
static InetAddressFilter internalAddresses() { ... }
static InetAddressFilter of(String... cidrs) { ... }
default InetAddressFilter and(InetAddressFilter other) { ... }
default InetAddressFilter andNot(String... cidrs) { ... }
default InetAddressFilter or(InetAddressFilter other) { ... }
}Declaring a @Bean of this type automatically applies it to all auto-configured HTTP clients: RestClient, RestTemplate, and WebClient (reactive).
What externalAddresses() Blocks — Source-Level Breakdown
externalAddresses()is implemented as address -> !internalMatcher.matches(address), where internalMatcher is an InetAddressMatcher covering the following IPv4 CIDR blocks (per RFC 1918, RFC 3927, RFC 5735, RFC 3171, RFC 1112):
Loopback: 127.0.0.0/8 (localhost)
Private A: 10.0.0.0/8 (RFC 1918)
Private B: 172.16.0.0/12 (RFC 1918)
Private C: 192.168.0.0/16 (RFC 1918)
Link-local: 169.254.0.0/16 (cloud metadata services — AWS, Azure, GCP, Huawei Cloud)
Unspecified: 0.0.0.0/8 (all interfaces on host)
Multicast: 224.0.0.0/4 Reserved: 240.0.0.0/4 IPv6 equivalents:
Loopback: ::1/128 Link-local: fe80::/10 Unique local (RFC 4193): fc00::/7 Multicast: ff00::/8 Key insight : 169.254.169.254 (used by AWS, Azure, GCP, Huawei Cloud) falls inside 169.254.0.0/16, so externalAddresses() blocks it automatically.
Alibaba Cloud metadata at 100.100.100.200 resides in 100.64.0.0/10 (CGN shared address space), which is not covered by the default internal ranges. The article recommends adding .andNot("100.64.0.0/10") for Alibaba Cloud deployments.
Integration Point: DNS Resolution → IP Check → TCP Connect
The filter runs after DNS resolution but before TCP connection establishment . Flow:
1. Request: http://evil.com/path
2. DNS resolves evil.com → 169.254.169.254
3. InetAddressFilter.check(169.254.169.254) → false (link-local)
4. Exception thrown; request never sentThis defeats DNS rebinding because the check uses the resolved IP, not the hostname string.
Redirect Handling
On a 302 redirect, the client re-resolves the new Location header and runs the filter again:
1. Request http://evil.com/redirect → resolves to 1.2.3.4 (public, allowed)
2. Server responds 302 Location: http://169.254.169.254/
3. DNS resolves 169.254.169.254 → 169.254.169.254
4. InetAddressFilter.check(169.254.169.254) → false
5. Redirect abortedConfiguration Patterns
Allow only public internet (most common)
@Bean
public InetAddressFilter httpClientInetAddressFilter() {
return InetAddressFilter.externalAddresses();
}Whitelist: only specific internal subnet
return InetAddressFilter.of("192.168.1.0/24");Blacklist: public internet but exclude a specific range
return InetAddressFilter.externalAddresses().andNot("203.0.113.0/24");Allow public + specific internal subnet
return InetAddressFilter.externalAddresses()
.or(InetAddressFilter.of("192.168.1.0/24"));Allow subnet but exclude individual hosts
return InetAddressFilter.of("192.168.1.0/24")
.andNot("192.168.1.1", "192.168.1.10");Custom logic (e.g., resolve allowed hostnames at startup)
return address -> {
Set<InetAddress> allowed = Set.of(
InetAddress.getByName("api.example.com"),
InetAddress.getByName("cdn.example.com")
);
return allowed.contains(address);
};Cloud Metadata Coverage
AWS: 169.254.169.254 — ✅ blocked (link-local)
Azure: 169.254.169.254 — ✅ blocked
GCP: metadata.google.internal (resolves to 169.254.169.254) — ✅ blocked
Huawei Cloud: 169.254.169.254 — ✅ blocked
Alibaba Cloud: 100.100.100.200 — ⚠️ not blocked by default (in 100.64.0.0/10 CGN block)
Tencent Cloud: metadata.tencentyun.com — depends on resolved IP
Defense-in-Depth: IMDSv2
AWS recommends IMDSv2 (Instance Metadata Service Version 2), which requires a PUT request to obtain a session token before metadata can be read. Since SSRF payloads are typically GET-only, IMDSv2 mitigates metadata theft at the platform layer. InetAddressFilter provides a second, application-layer barrier — defense in depth.
Caveats and Limitations
Only protects HTTP clients created via Spring Boot auto-configuration ( RestClient.Builder, RestTemplateBuilder, WebClient.Builder). Manually instantiated clients (e.g., new RestTemplate(), raw HttpClient) are not covered.
Alibaba Cloud (and any provider using CGN space 100.64.0.0/10) requires explicit .andNot("100.64.0.0/10").
The filter is one layer; combine with network security groups, IMDSv2, input validation, and least-privilege IAM roles.
Security is not a single component's job — it's defense in depth. InetAddressFilter is an application-layer fence; pair it with network-layer, cloud-platform-layer, and architecture-layer controls to truly stop SSRF. Since Spring Boot now ships a one-line solution, there's no reason not to enable it.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
Java Tech Workshop
Focused on Java backend technologies, sharing fundamentals, multithreading, JVM, the Spring ecosystem, microservices, distributed systems, high concurrency, source‑code analysis, and practical experience. Continuously delivers high‑quality original content, interview guides, and learning roadmaps to help Java developers progress from beginner to advanced, enhancing technical skills and core competitiveness.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
