Skip to content

Commit 1f991ea

Browse files
authored
fix(#5452): add optional ssrf check mechanism (#5464)
1 parent e75428c commit 1f991ea

17 files changed

Lines changed: 1092 additions & 20 deletions

File tree

.github/workflows/build-feature.yml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,18 @@ on:
88
- 2.*
99
pull_request:
1010

11+
concurrency:
12+
group: build-feature-${{ github.event.pull_request.head.ref || github.ref_name }}
13+
cancel-in-progress: true
14+
1115
jobs:
1216
build:
1317
strategy:
1418
matrix:
1519
os: [ubuntu-latest]
1620

1721
runs-on: ${{ matrix.os }}
22+
timeout-minutes: 30
1823
steps:
1924

2025
- name: free disk space

.github/workflows/build-main.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ jobs:
1515
os: [ ubuntu-latest ]
1616

1717
runs-on: ${{ matrix.os }}
18+
timeout-minutes: 30
1819
steps:
1920
- uses: actions/checkout@v7
2021

@@ -56,6 +57,7 @@ jobs:
5657
publish-snapshot:
5758
needs: build
5859
runs-on: ubuntu-latest
60+
timeout-minutes: 30
5961

6062
steps:
6163
- uses: actions/checkout@v7

spring-boot-admin-docs/src/site/docs/05-security/30-csrf-protection.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -758,4 +758,5 @@ public class SecurityConfig {
758758

759759
- [Server Authentication](./10-server-authentication.md) - Configure Spring Security
760760
- [Actuator Security](./20-actuator-security.md) - Secure client endpoints
761+
- [SSRF Protection](./40-ssrf-protection.md) - Block SSRF attacks via the instance registration endpoint
761762
- [Spring Security CSRF Documentation](https://docs.spring.io/spring-security/reference/servlet/exploits/csrf.html)
Lines changed: 242 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,242 @@
1+
---
2+
sidebar_position: 40
3+
sidebar_custom_props:
4+
icon: 'shield'
5+
---
6+
7+
# SSRF Protection
8+
9+
Spring Boot Admin Server includes opt-in protection against Server-Side Request Forgery (SSRF) attacks that can be
10+
triggered through the instance registration API.
11+
12+
## The Risk
13+
14+
When a Spring Boot application registers with the Admin Server, it sends a `POST /instances` request containing
15+
`healthUrl`, `managementUrl`, and `serviceUrl`. The Admin Server immediately begins making outbound HTTP requests to
16+
those URLs to poll health status and discover actuator endpoints. It also exposes a proxy at
17+
`/instances/{id}/actuator/**` that forwards requests verbatim to the registered management URL.
18+
19+
Without authentication or URL validation on `POST /instances`, an attacker can:
20+
21+
1. Register a fake instance pointing to `http://169.254.169.254/latest/meta-data/` (AWS IMDSv1)
22+
2. The Admin Server polls the metadata endpoint automatically and stores the response
23+
3. The attacker retrieves the response — including IAM credentials — through the actuator proxy
24+
25+
This applies to loopback addresses, RFC 1918 private ranges, and any internal service reachable from the server's
26+
network, not just cloud metadata endpoints.
27+
28+
:::warning
29+
SSRF protection is **disabled by default** to avoid breaking existing deployments where the Admin Server legitimately
30+
communicates with services on private IP ranges. Enable it explicitly and provide a custom `InetAddressFilter` bean
31+
for any intranet services that need to register.
32+
:::
33+
34+
---
35+
36+
## Enabling SSRF Protection
37+
38+
Add the following to your `application.yml`:
39+
40+
```yaml
41+
spring:
42+
boot:
43+
admin:
44+
ssrf-protection:
45+
enabled: true
46+
```
47+
48+
When enabled, all URLs submitted during instance registration (`healthUrl`, `managementUrl`, `serviceUrl`) are
49+
validated before the instance is stored. The proxy also re-validates the resolved target URL before each outbound
50+
request.
51+
52+
---
53+
54+
## What Gets Blocked
55+
56+
When protection is enabled, address filtering is delegated to Spring Boot's
57+
[`InetAddressFilter`](https://docs.spring.io/spring-boot/4.1/api/java/org/springframework/boot/http/client/InetAddressFilter.html).
58+
The default filter (`InetAddressFilter.externalAddresses()`) blocks all non-external addresses, including:
59+
60+
| Category | Examples |
61+
|---|---|
62+
| Loopback | `localhost`, `127.0.0.1`, `127.x.x.x`, `::1` |
63+
| Link-local (cloud metadata) | `169.254.0.0/16` — includes AWS IMDSv1 (`169.254.169.254`), GCP, Azure |
64+
| RFC 1918 Class A | `10.0.0.0/8` |
65+
| RFC 1918 Class B | `172.16.0.0/12` (172.16.x – 172.31.x) |
66+
| RFC 1918 Class C | `192.168.0.0/16` |
67+
| IPv6 link-local | `fe80::/10` |
68+
| IPv6 unique-local | `fc00::/7` (fc and fd prefixes) |
69+
| Unspecified address | `0.0.0.0` |
70+
| Disallowed schemes | Anything other than `http` and `https` (e.g. `file://`, `ftp://`) |
71+
72+
Registration attempts targeting any of these return `400 Bad Request`. Proxy requests that resolve to a blocked address
73+
return `403 Forbidden`.
74+
75+
:::note
76+
Hostnames are **resolved via DNS** during validation. This means a public hostname that resolves to a private IP
77+
(DNS rebinding) is also blocked — not just hostnames that literally look like private addresses.
78+
:::
79+
80+
---
81+
82+
## Allowing Internal Services
83+
84+
If your Admin Server legitimately needs to reach services on private addresses — for example in an on-premises or
85+
Kubernetes cluster deployment — you have two options:
86+
87+
### Option 1: Configuration properties (recommended)
88+
89+
Add entries to `allowed-cidrs`. Both individual IP addresses and CIDR subnet masks are supported for IPv4 and IPv6.
90+
These ranges are ORed with the default `externalAddresses()` filter, so public addresses always remain reachable.
91+
92+
**Single host** (exact IP address):
93+
94+
```yaml
95+
spring:
96+
boot:
97+
admin:
98+
ssrf-protection:
99+
enabled: true
100+
allowed-cidrs:
101+
- "192.168.1.100" # single host, equivalent to 192.168.1.100/32
102+
```
103+
104+
**Subnet** (CIDR mask):
105+
106+
```yaml
107+
spring:
108+
boot:
109+
admin:
110+
ssrf-protection:
111+
enabled: true
112+
allowed-cidrs:
113+
- "192.168.1.0/24" # all hosts in 192.168.1.x
114+
```
115+
116+
**Multiple ranges** (subnets, whole classes, IPv6):
117+
118+
```yaml
119+
spring:
120+
boot:
121+
admin:
122+
ssrf-protection:
123+
enabled: true
124+
allowed-cidrs:
125+
- "192.168.1.0/24" # single subnet
126+
- "10.0.0.0/8" # entire RFC 1918 Class A
127+
- "172.16.0.0/12" # entire RFC 1918 Class B
128+
- "fd00::/8" # IPv6 unique-local range
129+
```
130+
131+
### Option 2: Custom `InetAddressFilter` bean
132+
133+
For more control, expose an `InetAddressFilter` bean. It replaces the auto-configured filter entirely.
134+
135+
```java
136+
import org.springframework.boot.http.client.InetAddressFilter;
137+
import org.springframework.context.annotation.Bean;
138+
import org.springframework.context.annotation.Configuration;
139+
140+
@Configuration
141+
public class SsrfConfig {
142+
143+
@Bean
144+
public InetAddressFilter ssrfInetAddressFilter() {
145+
return InetAddressFilter.externalAddresses()
146+
.or("10.0.0.0/8") // pod CIDR
147+
.or("172.16.0.0/12"); // service CIDR
148+
}
149+
}
150+
```
151+
152+
`InetAddressFilter` supports CIDR notation for both IPv4 and IPv6, and can be composed with `.or()`, `.and()`, and
153+
`.andNot()`. See the
154+
[Spring Boot reference documentation](https://docs.spring.io/spring-boot/4.1/reference/io/rest-client.html#io.rest-client.global-configuration.inetaddress-filtering)
155+
for the full API.
156+
157+
---
158+
159+
## Allowing Additional Schemes
160+
161+
By default only `http` and `https` are permitted. To add a custom scheme:
162+
163+
```yaml
164+
spring:
165+
boot:
166+
admin:
167+
ssrf-protection:
168+
enabled: true
169+
allowed-schemes:
170+
- http
171+
- https
172+
- grpc
173+
```
174+
175+
---
176+
177+
## Configuration Reference
178+
179+
| Property | Type | Default | Description |
180+
|---|---|---|---|
181+
| `spring.boot.admin.ssrf-protection.enabled` | `boolean` | `false` | Enable SSRF URL validation |
182+
| `spring.boot.admin.ssrf-protection.allowed-schemes` | `Set<String>` | `http, https` | URL schemes that are permitted |
183+
| `spring.boot.admin.ssrf-protection.allowed-cidrs` | `List<String>` | _(empty)_ | IP addresses or CIDR ranges (IPv4 and IPv6) that are permitted in addition to public addresses. Examples: `192.168.1.0/24`, `10.0.0.0/8`, `fd00::/8` |
184+
185+
For finer-grained control over address filtering, expose a custom `InetAddressFilter` bean (see [Allowing Internal Services](#allowing-internal-services)).
186+
187+
---
188+
189+
## Providing a Custom Validator
190+
191+
Override the entire `SsrfUrlValidator` bean to implement fully custom logic:
192+
193+
```java
194+
import de.codecentric.boot.admin.server.utils.SsrfUrlValidator;
195+
import de.codecentric.boot.admin.server.config.AdminServerProperties;
196+
import org.springframework.boot.http.client.InetAddressFilter;
197+
import org.springframework.context.annotation.Bean;
198+
import org.springframework.context.annotation.Configuration;
199+
200+
@Configuration
201+
public class CustomSsrfConfig {
202+
203+
@Bean
204+
public SsrfUrlValidator ssrfUrlValidator(AdminServerProperties properties,
205+
InetAddressFilter ssrfInetAddressFilter) {
206+
return new SsrfUrlValidator(properties.getSsrfProtection(), ssrfInetAddressFilter);
207+
}
208+
}
209+
```
210+
211+
---
212+
213+
## Dual Validation
214+
215+
SSRF protection runs at two points:
216+
217+
1. **Registration (`POST /instances`)** — `healthUrl`, `managementUrl`, and `serviceUrl` are validated before the
218+
instance is stored. Invalid registrations are rejected with `400 Bad Request`.
219+
220+
2. **Proxy (`/instances/{id}/actuator/**`)** — The resolved target URL is re-validated before each outbound request.
221+
This provides defence-in-depth against scenarios where an endpoint URL is assembled dynamically after registration.
222+
Blocked proxy requests return `403 Forbidden`.
223+
224+
---
225+
226+
## Additional Hardening
227+
228+
SSRF protection alone is not sufficient for a publicly accessible Admin Server. Combine it with:
229+
230+
- **Authentication on `POST /instances`** — The most effective mitigation. See [Server Authentication](./10-server-authentication.md).
231+
- **AWS IMDSv2** — Require a `PUT` request with a TTL header to obtain a metadata token, which the Admin Server cannot
232+
provide without explicit support.
233+
- **Network egress controls** — Firewall rules or security groups that prevent the Admin Server's outbound traffic from
234+
reaching metadata endpoints and internal services.
235+
- **VPC/private network isolation** — Run the Admin Server in a subnet that has no route to sensitive internal services.
236+
237+
---
238+
239+
## See Also
240+
241+
- [Server Authentication](./10-server-authentication.md) - Require authentication before instance registration
242+
- [CSRF Protection](./30-csrf-protection.md) - Protect the registration endpoint against cross-origin forged requests

spring-boot-admin-docs/src/site/docs/05-security/index.md

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,18 @@ Configure CSRF tokens for Admin UI while allowing client registration:
5252

5353
**See**: [CSRF Protection](./30-csrf-protection.md)
5454

55-
### 4. Mutual TLS (Optional)
55+
### 4. SSRF Protection
56+
57+
Prevent Server-Side Request Forgery via the instance registration API:
58+
59+
- **IP Range Blocking**: Reject private/internal addresses in registered URLs
60+
- **Scheme Allowlist**: Permit only `http` and `https`
61+
- **Allowlist Override**: Explicitly permit intranet services by hostname
62+
- **Proxy-time Validation**: Re-validate resolved URLs before each outbound request
63+
64+
**See**: [SSRF Protection](./40-ssrf-protection.md)
65+
66+
### 5. Mutual TLS (Optional)
5667

5768
Enhanced security with client certificates:
5869

@@ -144,6 +155,7 @@ Use this checklist to ensure your deployment is secure:
144155
- [ ] Configure form login for UI access
145156
- [ ] Enable HTTP Basic for API/programmatic access
146157
- [ ] Configure CSRF protection with exemptions for `/instances`
158+
- [ ] Enable SSRF protection (`spring.boot.admin.ssrf-protection.enabled=true`)
147159
- [ ] Set up remember-me with secure random key
148160
- [ ] Use HTTPS for deployments
149161
- [ ] Restrict access by IP (if applicable)
@@ -476,6 +488,7 @@ public InMemoryUserDetailsManager userDetailsService(PasswordEncoder encoder) {
476488
- [Server Authentication](./10-server-authentication.md) - Secure Admin Server with Spring Security
477489
- [Actuator Security](./20-actuator-security.md) - Secure client actuator endpoints
478490
- [CSRF Protection](./30-csrf-protection.md) - Configure CSRF for UI and API
491+
- [SSRF Protection](./40-ssrf-protection.md) - Block SSRF attacks via instance registration
479492

480493
---
481494

0 commit comments

Comments
 (0)