Why 169.254.169.254 Should Scare You

In March 2019, a single text field — the kind that says "enter a URL" — was the first domino in a chain that ended with 106 million people's Social Security numbers and bank account details sitting in an attacker's hands. No zero-day. No supply chain compromise. Just a server that would fetch whatever URL you gave it.
What actually happened
The vulnerability was in a misconfigured web application firewall (ModSecurity) sitting in front of Capital One's AWS environment. According to the Wiz cloud threat landscape writeup and a peer-reviewed ACM Transactions on Privacy and Security analysis of the breach, the WAF could be tricked into making outbound requests on the attacker's behalf — a textbook Server-Side Request Forgery, or SSRF, which OWASP defines as an attacker abusing server-side functionality to make the server issue requests to destinations it wouldn't otherwise reach.
The attacker pointed that request at http://169.254.169.254/latest/meta-data/iam/security-credentials/ — the AWS instance metadata service, reachable only from inside the instance itself. No authentication required, no special headers, just a GET request. It handed back temporary IAM credentials scoped to that EC2 instance. Those credentials turned out to be over-permissioned, and the attacker used them to enumerate and download S3 buckets. That's the whole chain: one URL field, one internal-only endpoint, one over-broad IAM role, 106 million records.
Why that specific IP address
169.254.169.254 isn't a coincidence — it's a link-local address (RFC 3927), meaning it only resolves to something meaningful from inside the host that owns it. Every major cloud provider uses an address in that block to serve instance metadata: AWS, Azure, and GCP all put credentials, instance identity documents, and configuration data behind it, precisely because it's assumed nothing outside the instance can reach it.
SSRF breaks that assumption. If a server can be tricked into making one HTTP request to any attacker-chosen URL, "internal-only" stops meaning anything — the server itself becomes the attacker's proxy into the network it's sitting on.
What AWS actually changed
This wasn't a one-off. AWS's own response was to ship IMDSv2 in late 2019 — a session-oriented version of the metadata service that requires a PUT request carrying a custom header to obtain a short-lived token before any metadata can be read. The design is deliberate: most SSRF vulnerabilities only let an attacker control the URL of a GET request, not add custom headers or switch the HTTP method. Requiring a PUT-plus-header handshake first closes off exactly the class of attack that hit Capital One, even in a server that's still vulnerable to SSRF in every other sense. By 2024 AWS moved to make IMDSv2 the default for new instances industry-wide.
Why a mock API tool has an SSRF checklist at all
We ran into a version of this problem directly while building record & replay for MockBase: point a mock at a real API, and it proxies through any request that doesn't match a configured route. That's a text field where a user types in a URL, and the server fetches it — precisely the shape of feature OWASP's SSRF page exists to warn about, except here it's not a bug, it's the entire point of the feature.
So the proxy resolves the hostname before connecting and refuses anything that comes back private, loopback, link-local, or otherwise non-routable — the 169.254.169.254 block included. That check runs on every request, not just when the URL is first saved, because the target a user configured yesterday could resolve somewhere else today.
It's not airtight. A determined attacker with DNS control could still win a race between that check and the actual connection — classic DNS rebinding, and closing it properly means resolving the name once and connecting to that specific IP rather than trusting DNS twice, which we don't currently do. Worth being honest about: an allowlist check at request time raises the bar significantly, but it isn't the same guarantee as a network-level egress control that never trusts DNS in the first place.
The takeaway
If your server accepts a URL from a user and then fetches it — a webhook target, a callback URL, an "import from a link" feature, or, yes, a mock proxy target — you've built the exact shape of feature that turned one WAF misconfiguration into the largest breach in a major bank's history. The fix isn't complicated: resolve before you connect, and reject anything that isn't a public, routable address. The hard part is remembering to do it on a feature that, on the surface, has nothing to do with security at all.