Capital One 2019: SSRF, AWS IMDS, and 106 Million Exposed Records
On March 22, 2019, a single HTTP request to an IP address present on every AWS EC2 instance returned temporary IAM credentials with access to over 700 S3 buckets. No zero-day. No nation-state tooling. One SSRF and an overprivileged role.
The Capital One breach was determined by IAM design, not WAF configuration. The SSRF opened the door in one HTTP request, but the IAM role attached to the WAF instance, ISRM-WAF-Role, decided 700 buckets were in scope. A properly scoped WAF needs no S3 data access at all. The blast radius could have been zero bytes.
One HTTP Request, 700 S3 Buckets
The full attack chain ran in three steps, each one amplifying the blast radius of the previous. The SSRF delivered credentials. The credentials opened the AWS API. The overprivileged IAM role turned API access into catastrophic exposure.
IMDS exposes the active role name with no authentication. The first GET to http://169.254.169.254/latest/meta-data/iam/security-credentials/ returned ISRM-WAF-Role. The second GET to http://169.254.169.254/latest/meta-data/iam/security-credentials/ISRM-WAF-Role returned a JSON body with AccessKeyId, SecretAccessKey, Token, and Expiration. With those STS credentials, Paige Thompson ran aws s3 ls from outside AWS and listed over 700 buckets.
s3:GetObject calls that followed pulled approximately 30 GB over two days, March 22-23, 2019. The data covered 106 million credit application records: 100 million US customers and 6 million in Canada, spanning 2005 to 2019. That included 140,000 Social Security numbers, 80,000 bank account numbers, plus payment histories, credit scores, and credit limits.
The breach went undetected for four months. On July 17, 2019, a GitHub user alerted Capital One about stolen data posted by Thompson under the alias Erratic. Capital One notified the FBI on July 19. No internal alert fired during the two days 30 GB was exfiltrated from the infrastructure.
169.254.169.254: The Credential Dispenser With No Authentication
IMDSv1 was designed for operational convenience, not adversarial environments. Any process running on the EC2 instance, including processes manipulated via SSRF, could retrieve full IAM credentials without any token, signature, or identity check.
169.254.169.254 is a link-local address: it exists only within the instance's own network stack. Link-local routing means the WAF instance appears as the requester to the metadata service. An SSRF in the application turns the WAF instance into an involuntary relay: the request arrives at the application, the application directs the WAF to issue the GET to IMDS, and IMDS responds with valid STS credentials in plain text.
The returned credentials are real AWS credentials, with AccessKeyId, SecretAccessKey, Token, and Expiration fields. They work from any configured AWS client on any network, not just from inside the VPC. STS credential validity varies by role, but typically lasts hours, long enough to complete an exfiltration at scale.
AWS released IMDSv2 on November 19, 2019, seven months after the breach. IMDSv2 requires a PUT handshake with the X-aws-ec2-metadata-token-ttl-seconds header to obtain a session token before any metadata GET. A hop-limit of 1 on the PUT packet's IP TTL ensures the response token is dropped after one network hop, breaking SSRF chains that pass through proxies or intermediate WAFs.
IMDSv2 addresses the specific SSRF-to-IMDS vector used against Capital One. It does not protect against arbitrary code execution on the instance, where the attacker sits on the same network hop and can complete the PUT handshake normally. And it does nothing to reduce the blast radius of an overprivileged IAM role: the permissions policy remains the deciding factor.
The IAM Role That Controlled Every Bucket
The blast radius of the Capital One breach was entirely determined by what ISRM-WAF-Role had permission to do. That is an IAM design decision, not an inevitable attack outcome. A WAF has no operational reason to read customer financial PII stored in S3.
Based on reconstructed court records, ISRM-WAF-Role held permissions consistent with s3:* on Resource: *, putting over 700 buckets in scope. A properly scoped WAF role would need at most s3:GetObject on a specific WAF configuration bucket. The difference between Resource: * and arn:aws:s3:::waf-config-prod/* is the difference between 30 GB exfiltrated and zero bytes.
The OCC imposed an $80 million civil money penalty on Capital One on August 6, 2020, the first significant regulatory penalty against a bank for a cloud security failure. The OCC cited failure to establish adequate risk management controls for the cloud environment, absence of effective data loss prevention controls, and lack of effective alert systems. The Cleary Gottlieb analysis of the OCC enforcement order confirms that regulatory action focused on governance deficiencies, not the specific WAF misconfiguration.
Paige Thompson, a former AWS engineer with direct knowledge of the IMDS endpoint, was sentenced to time served plus five years of probation. Prior knowledge of the endpoint accelerated identifying the attack vector. The overprivileged IAM policy is what determined the scale of the consequences.
Why ModSecurity Let It Through
ModSecurity's failure was necessary for Thompson's specific attack, but insufficient to explain the scale of the impact. ModSecurity, with its default ruleset, does not block outbound application requests to the 169.254.0.0/16 range. That is a documented configuration gap, not a software bug.
SSRF filter bypass techniques are numerous and well documented in offensive security literature. Decimal IP notation converts 169.254.169.254 to 2852039166, bypassing WAF rules based on string matching. Equivalent IPv6 notation and URL encoding offer alternative paths to the same address. Thompson built a custom scanner to identify misconfigured AWS accounts, drawing on her AWS engineering background to locate instances vulnerable to the IMDS vector.
The structural problem runs deeper than the ModSecurity ruleset. A WAF defends against inbound attacks, but an SSRF converts the WAF into an outbound attacker against its own internal environment. Perimeter controls cannot set an upper bound on blast radius because they do not control what happens after credentials are obtained. The OCC explicitly cited the absence of effective alert systems as a determining cause of the impact: CloudTrail data events for S3 existed as an available AWS capability, but volume-based alerts were not configured in Capital One's environment.
IMDSv2: What It Fixes and What It Does Not
IMDSv2 closes the specific SSRF-to-IMDS vector used against Capital One. The mechanism requires a PUT to http://169.254.169.254/latest/api/token with the X-aws-ec2-metadata-token-ttl-seconds header, which returns a session token with a configured TTL. Subsequent GET requests to IMDS must include that token in the X-aws-ec2-metadata-token header. SSRF attacks that can only issue GET requests do not complete the handshake and do not obtain credentials.
The hop-limit of 1 on the PUT token's IP packet is the critical control against SSRF chains that pass through intermediaries. When the PUT request traverses an additional network hop, such as a WAF or reverse proxy, the IP packet's TTL is decremented to zero and the response token is dropped by the operating system. The external attacker does not receive the token and cannot issue the subsequent IMDS GETs.
AWS made IMDSv2 the default for new instances in 2021. Existing instances require an opt-in migration by setting HttpTokens=required via console or CLI. An environment with older instances created before 2021 that have not been migrated still runs IMDSv1 by default.
IMDSv2 does not protect against arbitrary code execution on the instance, where the attacker is on the same hop and completes the PUT normally. It does not protect against SSRF variants in frameworks that allow issuing PUT requests with custom headers. And it does nothing to reduce the blast radius of an overprivileged IAM role: if the attacker reaches IMDS through any working vector, the role's permissions determine exactly what can be exfiltrated.
Detecting SSRF-to-IMDS Chains Before Exfiltration
Three detection layers would have identified the Capital One breach in real time. None were operational during the two days of 30 GB exfiltration.
GuardDuty generates the UnauthorizedAccess:IAMUser/InstanceCredentialExfiltration.OutsideAWS finding when EC2 instance credentials are used from an IP address outside AWS. Thompson exfiltrated from her home network. That finding would have fired on the first aws s3 ls call executed outside AWS infrastructure, before a single byte of data left the VPC.
S3 data events in CloudTrail plus rate-based volume alerts would have detected 700 buckets being listed and read by a WAF instance within 24 hours. The operational baseline for a WAF issuing s3:GetObject calls against credit application data is zero. Any deviation from that baseline is detectable with a CloudWatch threshold rule, no implementation complexity required.
Security Groups at the application layer denying outbound TCP to 169.254.0.0/16 contain the SSRF before credentials are reached. This control operates at the network layer, independent of WAF configuration or application code. intel.mago.team (MAGO team tool) identifies this class of misconfiguration through passive API surface analysis: EC2 roles with broad-scope s3:* and instances with IMDSv1 enabled are flagged before any active exploitation.
The OCC's $80 million fine did not change Thompson's sentence. It changed the IAM governance budget of every CISO at banks with cloud workloads. The blast radius of any future SSRF in your environment is set right now by your IAM role policies, not your perimeter controls.
Top comments (0)