Adam Innes · Blog

After Capital One: SSRF and the Metadata Endpoint

· 4 min · security, aws, cloud, ssrf

On July 29, Capital One announced a data security incident affecting about 100 million people in the United States and about 6 million in Canada. The company says the access happened on March 22 and 23, and it found out after an outside security researcher reported the issue through its responsible disclosure program on July 17. Capital One said the FBI had already arrested the person responsible.

Plenty of confident explanations have gone around since. I want to separate what the primary sources say from the general pattern people are pointing at, which is worth understanding either way.

What the official sources say

Capital One’s statement describes “a specific configuration vulnerability” in its infrastructure, which it fixed. It also says this kind of vulnerability isn’t unique to cloud environments, and that the access also enabled decrypting the data, although tokenized fields stayed protected. Social Security numbers were among the tokenized fields, and Capital One says more than 99% of them weren’t compromised, though it also says about 140,000 US Social Security numbers and about 1 million Canadian Social Insurance Numbers were.

The criminal complaint filed in federal court in Seattle on July 29 adds more. It says a firewall misconfiguration let commands reach and be executed by a particular server. According to the complaint, the first command obtained security credentials for an account with a name ending in WAF-Role, a second used that role to list folders or buckets in Capital One’s storage at a cloud computing company, and a third synced data out of the ones the role could read. Capital One also told investigators that this role doesn’t normally list buckets.

That’s where the official record stops. Neither document names a technique, so I won’t either. What I will say is that role credentials leaking from a server is a class of problem every team on EC2 should check for, and the best known way it happens is worth understanding whether or not it’s what happened here.

Where instance credentials live

When you attach an IAM role to an EC2 instance, code on that instance gets temporary credentials from the instance metadata service at http://169.254.169.254, under latest/meta-data/iam/security-credentials/ followed by the role name. The address is link local, so it only answers requests from the instance itself. The AWS SDKs and CLI pick the credentials up automatically, and AWS rotates them for you.

That beats long lived keys in a config file, but the only thing protecting that endpoint is “the request came from inside the box.” And those credentials aren’t tied to the box. AWS’s own docs explain how to use them from outside an instance by supplying the access key, secret key and session token.

How SSRF turns that into a leak

Server side request forgery is when an attacker gets your server to make an HTTP request of their choosing. Anything that fetches a URL for a user can be a candidate: image importers, link previews, webhook testers, PDF renderers, reverse proxies. If the attacker can steer that request to 169.254.169.254, the metadata service sees a local caller and answers. The response comes back through your app, and now someone else has your role’s keys.

AWS actually warns about this in the docs on retrieving role credentials. It says services that make HTTP calls on your behalf can expose credentials, and names HTTP proxies, HTML and CSS validators, and XML processors that support XML inclusion.

Defenses worth adding

Start with least privilege, which AWS lists among its IAM best practices. Ask what the role on each instance really needs. Most instances don’t need to list every bucket in the account or read customer data at all. If a role can only touch the two resources the app uses, a stolen copy is a much smaller problem. If an instance doesn’t need AWS access, don’t attach a role.

Next, keep application code away from the metadata service when it doesn’t need it. On Linux, a host firewall rule can limit who reaches that address by user. For example, if your app runs as www-data and never calls AWS APIs:

iptables -I OUTPUT -d 169.254.169.254 -m owner --uid-owner www-data -j REJECT

Then fix the fetch code itself. Resolve the hostname before connecting and refuse link local, loopback and private ranges, don’t follow redirects blindly, and allow list destinations when you can. A WAF rule that rejects requests containing 169.254.169.254 is a reasonable extra layer, but attackers can use redirects or DNS names that resolve to the same address, so it can’t be your only check.

Finally, watch how your roles behave. The complaint’s detail that the role doesn’t normally list buckets is exactly the kind of signal CloudTrail can surface if someone is looking.

The takeaway

We don’t have the full technical story of the Capital One breach yet, and I’d be careful with anyone who claims to. But the pattern of a server with too much reach that can be talked into leaking its own credentials is common enough that it’s worth an afternoon of checking your roles and your URL fetching code now.

← all posts