4.1 KiB
4.1 KiB
ssrf — SSRF mitigation with InetAddressFilter, vulnerable endpoint included
Companion project for
HTTP Client SSRF Mitigation in Spring Boot 4.1: The InetAddressFilter Everyone Will
Configure Backwards on ankurm.com.
One application, one deliberately vulnerable endpoint, and five filter configurations selected
by Spring profile. Every transcript under docs/output/ came from running it;
./scripts/run-all.sh regenerates all of them.
Versions
| Version | Notes | |
|---|---|---|
| JDK | 25 (Temurin 25.0.4.1+1) | current LTS |
| Spring Boot | 4.1.1 | 4.1.0 GA was 10 June 2026; InetAddressFilter is @since 4.1.0 |
| Spring Framework | 7.0.9 | Boot-managed |
| Apache HttpComponents | 5.6.4 | on the classpath deliberately — see chapter 4 |
| Tomcat | 11.0.24 | |
| JUnit Jupiter / AssertJ | Boot-managed | 4 assertions |
Versions were read from repo1.maven.org/.../maven-metadata.xml, not from release
announcements.
Quickstart
./scripts/run.sh # no filter bean at all
./scripts/exploit.sh # four targets, four sets of credentials
./scripts/run.sh docsfilter # InetAddressFilter.externalAddresses()
./scripts/exploit.sh # internal targets blocked, example.com still works
./scripts/run.sh blocklist # the inversion
./scripts/exploit.sh # RFC 1918 target succeeds, example.com fails
./scripts/run-all.sh # regenerate everything under docs/output/
Profiles
| Profile | Filter bean | What it shows |
|---|---|---|
| (none) | — | the exploit, working |
docsfilter |
externalAddresses() |
the reference documentation's recommendation, and it is correct |
blocklist |
of(RFC1918) |
the release notes' word "block", acted on: attack succeeds, legitimate call fails |
negated |
internalAddresses().negate() |
looks equivalent to externalAddresses(), differs on four rows |
allowlist |
externalAddresses().and(of(...)) |
naming your destinations, and what that costs when their DNS changes |
twofilters |
two beans | the context does not start, and the diagnostic blames RestClient |
Endpoints
| Endpoint | Purpose |
|---|---|
GET /preview?url= |
the vulnerable fetcher |
GET /internal/credentials |
the thing that must not be reachable |
GET /diag/filter?host= |
what the running context decided. Delete before shipping |
Documentation
- What SSRF actually costs you
- The exploit, start to finish
matchesmeans allow — the one that matters- Where the filter runs depends on your HTTP client
- Wiring it up, and the three ways it silently does nothing
- Operating it
- Composing filters, and the vararg that matches nothing
Captured output
| File | Produced by |
|---|---|
filter-matrix.txt |
FilterMatrix — 15 addresses × 9 filters |
exploit-by-profile.txt |
run-all.sh — five profiles, five targets each |
and-varargs-trap.txt |
AndVarargsTrap |
two-filter-beans.txt |
the twofilters startup failure |
tests.txt |
WhereTheFilterRunsTests |
The three findings worth carrying away
matchesmeans allow. The release notes say "block"; the reference documentation says "only allow". The second is right. Writingof(<ranges to forbid>)produces a filter that permits exactly what you meant to stop.internalAddresses().negate()is notexternalAddresses(). They disagree on CGNAT space,0.0.0.0, TEST-NET-1 and multicast — the negation allows all four.and("a", "b")matches nothing. Each address becomes a separate filter and they are ANDed. Wrap multiple addresses inof(...)first.