Skip to content
Security Notes

SSRF into cloud metadata: reproducing Capital One in a lab

By Elias Lankinen11 min read

I have enough well-sourced material. Writing the post now.

The email that ended it arrived on a Sunday

On July 17, 2019, someone sent a message to Capital One's responsible-disclosure inbox. It pointed to a GitHub page where a user had posted, in plain view, a list of files and the commands used to copy them. The files belonged to Capital One. Twelve days later the FBI arrested a 33-year-old former Amazon engineer named Paige Thompson at her home in Seattle, and the bank disclosed that the personal data of about 106 million people, roughly 100 million in the United States and 6 million in Canada, according to Brian Krebs, had been copied out of its cloud environment.

Source: Lluck002, Wikimedia Commons (CC BY-SA 4.0)
Source: Lluck002, Wikimedia Commons (CC BY-SA 4.0)

What makes the Capital One breach worth revisiting seven years later is not its size. It is how little the attacker needed. There was no zero-day exploit, no malware, no stolen employee password. The whole chain hinged on a web server being tricked into making a single HTTP request to an address it should never have been asked to visit: 169.254.169.254. That request handed back temporary cloud credentials, and the credentials did the rest. This is a class of bug called server-side request forgery, or SSRF. It is the kind of vulnerability you can reproduce, safely and legally, in a lab you build yourself in an afternoon. Doing so is the fastest way to understand why one misconfigured firewall cost Capital One an $80 million regulatory fine and a $190 million settlement with affected customers.

What server-side request forgery actually is

Start with the name, because it is precise. A web application often needs to fetch things from other servers on your behalf: pull an image from a URL you paste in, check a webhook, render a link preview, validate an address against a mapping API. The application makes those outbound requests using its own network position, not yours. Server-side request forgery is what happens when an attacker gets to choose the destination of one of those requests. You supply a URL, the server dutifully fetches it, and the server's fetch runs from inside the target's network, with the target's trust and the target's reachability. The forgery is that the request looks, to everything downstream, like it came from a legitimate internal component. That distinction is the whole game. A firewall keeps outsiders out. But an SSRF-vulnerable server is already an insider. When it makes a request on the attacker's behalf, it does so from behind the firewall, past the perimeter, with access to internal-only services that were never meant to face the internet. The attacker never touches those services directly. They just point a trusted server at them. The classic SSRF payload is boring on its face: an attacker changes a parameter from https://example.com/logo.png to http://169.254.169.254/. To understand why that specific address is the crown jewel, you have to know what lives there.

The magic address every cloud server can reach

Every EC2 instance, Amazon's name for a virtual server, can reach a special address: 169.254.169.254. It sits in the "link-local" range, a block reserved for addresses that are only meaningful on the local network segment and are never routed across the internet. On a cloud instance, that address answers with the Instance Metadata Service, or IMDS. IMDS is genuinely useful. It lets a running server ask questions about itself, its instance ID, its region, its network configuration, without hardcoding any of it. Software queries the service with an ordinary HTTP request and gets back plain text. No password, no token, no authentication of any kind in the original design. The reasoning was that only code already running on the instance could reach the address in the first place, so the local network boundary was the authentication. The dangerous part lives at a specific path. When an EC2 instance is assigned an IAM role, a bundle of cloud permissions granted to the machine rather than to a person, AWS delivers the working credentials for that role through the metadata service. As Datadog's security team describes, code on the instance reads them from:

http://169.254.169.254/latest/meta-data/iam/security-credentials/<role-name>

That endpoint returns a temporary access key, a secret key, and a session token as JSON. Those three values are, functionally, the machine's identity in the cloud. Anyone holding them can act as the instance until they expire, typically within a few hours. Now combine the two ideas. An SSRF vulnerability lets an attacker choose where a trusted server sends a request. The metadata service hands out live cloud credentials to anything on the instance that asks, no authentication required. Point the first at the second and the server fetches its own credentials and returns them to the attacker in the HTTP response. That is not a chain of exotic exploits. It is two reasonable design decisions colliding.

Source: Visitor7, Wikimedia Commons (CC BY-SA 3.0)
Source: Visitor7, Wikimedia Commons (CC BY-SA 3.0)

How the pieces fit in the Capital One chain

Capital One ran a web application firewall, a filtering layer that inspects incoming web traffic for malicious patterns, reportedly the open-source ModSecurity, on an EC2 instance in its AWS environment. The firewall was misconfigured in a way that let an outside request coax it into making a request of the attacker's choosing. That is the SSRF entry point. According to the federal criminal complaint as reported by Krebs and others, the attacker used the firewall to reach the metadata service, list the credentials there, and retrieve the temporary keys for a role the complaint redacted to a name ending in -WAF-Role. Then came the second failure, the one that turned a credential leak into a nine-figure breach. That role had far more permission than a firewall should ever need. It could list and read Capital One's storage buckets in S3, Amazon's object-storage service, the digital equivalent of a warehouse of files. In its written response to Senator Ron Wyden, who had demanded to know whether AWS itself was at fault, Amazon put it plainly: the attack "occurred due to a misconfiguration error at the application layer of a firewall installed by Capital One, exacerbated by permissions set by Capital One that were likely broader than intended." With those over-broad credentials, Thompson listed the buckets and synced roughly 30 gigabytes of data to her own machine, across intrusions running from March into July 2019. The haul, per Krebs's account of Capital One's disclosure, covered credit-card applications going back to 2005: names, addresses, dates of birth, self-reported income, and for smaller subsets the more damaging identifiers, around 140,000 Social Security numbers and 80,000 linked bank account numbers in the US, plus about a million Canadian Social Insurance Numbers. (The Justice Department later cited slightly different figures, around 120,000 SSNs and 77,000 bank accounts; the exact counts shifted as the investigation progressed.)

Reproducing it in a lab

The reason this breach is taught in security courses is that you can rebuild the essential mechanism in an account you own, against infrastructure you control, without touching anyone else's systems. That authorization boundary is the whole ethical line: doing this in your own lab is education, and pointing the same request at a server you do not own is a federal crime under the Computer Fraud and Abuse Act, the statute Thompson was ultimately convicted under. A minimal reproduction has three ingredients, mirroring the real chain. First, a vulnerable app. Stand up an EC2 instance and run a tiny web service with an endpoint that fetches a user-supplied URL, the "render this link" feature that every SSRF starts from. Something as small as a route that takes a url parameter and returns the body of whatever it fetches. No input validation. That is your firewall stand-in. Second, an over-permissioned role. Attach an IAM role to the instance and give it read access to a test S3 bucket you have filled with harmless dummy files. This is the excess-privilege mistake, isolated so you can see its effect. Third, the attack request. From your own laptop, call your vulnerable endpoint and ask it to fetch the metadata path instead of a normal URL. On an instance still allowing the original metadata protocol, the flow is exactly the two-step lookup the real attacker used: list the role, then read its credentials.

# Step 1: ask the vulnerable app to list roles via the metadata service
curl "http://YOUR-APP/fetch?url=http://169.254.169.254/latest/meta-data/iam/security-credentials/"
# -> returns the role name, e.g. lab-demo-role

# Step 2: ask it to read that role's credentials
curl "http://YOUR-APP/fetch?url=http://169.254.169.254/latest/meta-data/iam/security-credentials/lab-demo-role"
# -> returns AccessKeyId, SecretAccessKey, Token as JSON

Paste those three values into the AWS CLI as environment variables and run aws s3 ls. Your dummy bucket lists back. You have just walked the entire Capital One kill chain, from an unvalidated URL parameter to reading storage you were never authenticated to touch, and every packet stayed inside resources you own. Tearing it down afterward is a matter of terminating the instance and deleting the role.

The fix that rewrote the default

On November 19, 2019, four months after the breach became public, AWS's Colm MacCárthaigh announced IMDSv2, a redesigned metadata service built specifically to break the SSRF-to-credentials chain. It defends with two changes, and the elegance is that both are things a naive SSRF cannot do. The first is a session handshake. Before reading any metadata, software must make an HTTP PUT request to obtain a short-lived token, then include that token as a header on every subsequent request:

TOKEN=$(curl -X PUT "http://169.254.169.254/latest/api/token" \
  -H "X-aws-ec2-metadata-token-ttl-seconds: 21600")

curl -H "X-aws-ec2-metadata-token: $TOKEN" \
  http://169.254.169.254/latest/meta-data/

Most SSRF vulnerabilities can only make simple GET requests. As MacCárthaigh noted, the "vast majority do not permit HTTP PUT requests," and an attacker who controls only the URL generally cannot set an arbitrary request header either. The token requirement quietly rules out the entire class of basic URL-substitution attacks. The second change is more clever. IMDSv2 sets the network hop limit of its response, the maximum number of routers a packet may cross, to 1 by default. A request originating on the instance itself gets its answer before that counter runs out. But if the metadata request has been relayed through something, a misconfigured firewall, a reverse proxy, a NAT device, the extra hop decrements the counter to zero and the packet is dropped before it leaves the box. AWS also made IMDSv2 refuse to issue tokens to any request carrying an X-Forwarded-For header, the fingerprint of a proxy. In other words, the service now actively detects the exact relay pattern that a WAF-based SSRF depends on and refuses to play along. There is an honest caveat here, and it is worth stating rather than flattening. IMDSv2 is not a magic shield against every SSRF. Security researcher Yassine Aboukir documented a real case where a more powerful SSRF, one that let the attacker control the HTTP method and headers through an Atlassian Confluence flaw, could still complete the PUT handshake and read credentials from IMDSv2. The defense raises the bar dramatically; it does not remove the endpoint. The durable fix is to require IMDSv2 and keep instance roles scoped to only what they need, so that even leaked credentials open few doors.

The misconception worth correcting

The single most common way this breach gets retold is "Capital One was hacked through an Amazon vulnerability." That framing is wrong in a way that matters, and untangling it is the practical lesson. Nothing in AWS was exploited. The metadata service behaved exactly as designed. The firewall was misconfigured by Capital One, the role was over-permissioned by Capital One, and the buckets were readable by that role because of choices Capital One made. This is what cloud providers call the shared responsibility model: AWS secures the infrastructure, the customer secures what they build on it. The regulator agreed with that division of blame. The Office of the Comptroller of the Currency fined Capital One $80 million not because AWS failed, but because the bank "failed to establish effective risk assessment processes" before moving critical operations to the cloud. Where it gets genuinely contested is whether the original IMDS design shares moral responsibility even if it shares no legal responsibility. Senator Wyden pressed exactly this point, asking why a service that hands out credentials to any unauthenticated local request existed in the form it did. AWS's own rapid release of IMDSv2, and its later moves to make the hardened version the default on new instances, can reasonably be read as a tacit answer. A default that is safe only when everything upstream is perfectly configured is a default that will eventually fail, because upstream is never perfectly configured. That is a design lesson, not a Capital One lesson.

What to watch

Paige Thompson was convicted on seven counts in June 2022 and, in October 2022, sentenced to time served and five years of probation, a punishment prosecutors called far too lenient after asking for seven years in prison. The legal aftermath is, in that sense, closed. The technical aftermath is not. The original metadata protocol still exists on countless running instances, because turning it off can break older software that never learned the token handshake, and organizations are slow to migrate what still works. Every cloud has its own metadata endpoint with its own history: Google Cloud and Azure expose comparable services, and both have had to add their own guardrails, such as required custom headers, against the same attack shape. SSRF itself has only climbed in prominence, earning its own category in the OWASP Top 10 the year after the breach. So the question the Capital One case leaves open is not "how did this happen" but "how many instances out there are still one unvalidated URL parameter away from the same outcome." The exploit is trivial to reproduce in a lab precisely because it is trivial to reproduce in the wild. The defense exists, it is free, and it has for years. What remains is the unglamorous work of turning it on everywhere, and scoping every role as if its credentials will one day leak, because eventually one will.

Sources

  1. Krebs on Security, "Capital One Data Theft Impacts 106M People", 2019.
  2. Office of the Comptroller of the Currency, "OCC Assesses $80 Million Civil Money Penalty Against Capital One", 2020.
  3. AWS Security Blog, Colm MacCárthaigh, "Add defense in depth against open firewalls, reverse proxies, and SSRF vulnerabilities with enhancements to the EC2 Instance Metadata Service", 2019.
  4. Datadog Security Labs, "Misconfiguration Spotlight: Securing the EC2 Instance Metadata Service", 2023.
  5. Yassine Aboukir, "Exploitation of an SSRF vulnerability against EC2 IMDSv2", 2023.
  6. BankInfoSecurity, "Capital One: Where Did the Bank Fail on Defense?", 2019.
  7. BankInfoSecurity, "Capital One Hacker Paige Thompson Sentenced to Time Served", 2022.
  8. TechTarget, "Paige Thompson found guilty in 2019 Capital One data breach", 2022.
  9. Cybersecurity Dive, "Capital One freed from consent order tied to 2019 breach", 2022.
  10. U.S. Securities and Exchange Commission, Capital One Financial Corp. Form 10-Q filings, FY2019–FY2020.