50 lines
2.2 KiB
Markdown
50 lines
2.2 KiB
Markdown
[← What SSRF costs you](01-what-ssrf-costs-you.md) · [Module README](../README.md) · [Allow, not block →](03-allow-not-block.md)
|
|
|
|
# 2. The exploit, start to finish
|
|
|
|
Run the application with no profile, so there is no `InetAddressFilter` bean at all:
|
|
|
|
```bash
|
|
./scripts/run.sh
|
|
./scripts/exploit.sh
|
|
```
|
|
|
|
The transcript is committed at
|
|
[`docs/output/exploit-by-profile.txt`](output/exploit-by-profile.txt). The first block is this
|
|
one:
|
|
|
|
```
|
|
http://127.0.0.1:8080/internal/credentials FETCHED | {"SecretAccessKey":"wJalrXUtnFEMI-...
|
|
http://localhost:8080/internal/credentials FETCHED | {"SecretAccessKey":"wJalrXUtnFEMI-...
|
|
http://[::1]:8080/internal/credentials FETCHED | {"SecretAccessKey":"wJalrXUtnFEMI-...
|
|
http://172.16.10.3:8080/internal/credentials FETCHED | {"SecretAccessKey":"wJalrXUtnFEMI-...
|
|
http://example.com/ FETCHED | <!doctype html>...
|
|
```
|
|
|
|
Four targets, four sets of credentials, and the legitimate outbound call still works. That last
|
|
row matters as much as the others: any mitigation has to leave it intact, and one of the
|
|
configurations in [chapter 3](03-allow-not-block.md) does not.
|
|
|
|
## Why four targets and not one
|
|
|
|
`127.0.0.1` is the one every tutorial blocks. The others are why a hand-written check usually
|
|
leaks:
|
|
|
|
| Target | What it defeats |
|
|
|---|---|
|
|
| `localhost` | checks written against the literal string `127.0.0.1` |
|
|
| `[::1]` | checks that only ever consider IPv4 |
|
|
| `172.16.10.3` | checks that stop at loopback and forget RFC 1918 |
|
|
|
|
The fourth is this container's own address on its network interface. It is the same process,
|
|
reached the same way, over a route that a loopback-only rule does not cover. In a real
|
|
deployment it is the pod next door.
|
|
|
|
Blocking by string is hopeless in a way that is easy to underestimate. `0x7f.0.0.1`,
|
|
`2130706433`, `127.1`, a DNS name you control that resolves to `127.0.0.1`, and a redirect from a
|
|
public URL to a private one all reach loopback without the string `127.0.0.1` appearing anywhere
|
|
in the request. This is why the check belongs at address-resolution time and not in a validator
|
|
over the URL — which is exactly what `InetAddressFilter` is.
|
|
|
|
[Allow, not block →](03-allow-not-block.md)
|