Cloud Security Office Hours Banner

Capital One 2019 — 2019

Step-by-step kill chain mapped to MITRE ATT&CK Cloud, sourced from official post-mortems and primary technical analyses.

March–July 2019 Critical AWS

Capital One – SSRF → IMDSv1 → Over-Privileged IAM Role → 106M Record S3 Exfiltration

A former AWS engineer exploited a misconfigured WAF via server-side request forgery to reach the EC2 instance metadata service, stealing temporary IAM role credentials. An over-privileged role then granted access to 700+ S3 buckets containing 106 million customer records. The attack ran undetected for 77 days and directly caused AWS to build IMDSv2.

106M records exposed
77 days dwell time
$80M civil penalty
Threat actor: Paige Thompson, former AWS employee
📄 KrebsOnSecurity post-mortem ↗ 📄 Appsecco technical analysis ↗ 📄 AWS IMDSv2 response ↗
🔍 Reconnaissance
01
Automated scanning for AWS-hosted apps vulnerable to SSRF
T1595 – Active Scanning

Thompson built a custom tool to scan the internet for EC2-hosted web applications that would relay requests to the AWS instance metadata service at 169.254.169.254. SSRF was not in ModSecurity's default detection rule set — it had to be explicitly configured.

Target: Public-facing ModSecurity WAF running on EC2
Why SSRF wasn't blocked: Not in default WAF rules — required manual configuration
Scanning approach: Automated — targeted multiple AWS-hosted organisations
SSRFEC2WAFModSecurity
02
SSRF exploitation — WAF tricked into relaying requests to IMDS
T1190 – Exploit Public-Facing App

The WAF was misconfigured — running in logging-only mode or bypassable — so Thompson sent crafted HTTP requests containing the IMDS endpoint as the target URL. The WAF relayed these server-side, making the EC2 instance itself issue the metadata request.

SSRF payload target: http://169.254.169.254/latest/meta-data/
IMDSv1 behaviour: No authentication — any GET request from the instance is served
WAF failure: Relayed SSRF payload rather than blocking it
SSRFIMDSv1T1190169.254.169.254
🔑 Credential Access
03
EC2 IAM role credentials retrieved from IMDS — no auth required
T1552.005 – Cloud Instance Metadata API

IMDSv1 returned the temporary AWS credentials (AccessKeyId, SecretAccessKey, SessionToken) for the "ISRM-WAF-Role" attached to the EC2 instance. No token, header, or authentication required — just a GET request to the metadata path from within the instance (which the SSRF provided).

IMDS path: /latest/meta-data/iam/security-credentials/ISRM-WAF-Role
Credentials returned: AccessKeyId + SecretAccessKey + SessionToken
Key problem: The role had S3 permissions far beyond what a WAF needs
IMDSv1ISRM-WAF-RoleTemp CredentialsT1552.005
🔼 Privilege Abuse — Over-Permissioned IAM Role
04
700+ S3 buckets enumerated using stolen role credentials
T1619 – Cloud Storage Object Discovery

With the stolen AWS credentials, Thompson used the CLI to list all S3 buckets accessible to the ISRM-WAF-Role. The role had been granted sweeping S3 list and read permissions — far beyond what a WAF firewall function ever needed — violating least privilege at the design level.

Command: aws s3 ls (authenticated with stolen session credentials)
Result: 700+ buckets listed including Capital One customer data stores
Root failure: IAM role permissions never reviewed against principle of least privilege
IAMS3Least Privilege ViolationT1619
05
30GB bulk S3 exfiltration — 106M customer records
T1530 – Data from Cloud Storage

Thompson synced S3 bucket contents to external storage using aws s3 sync. Approximately 30GB over multiple sessions — 100M US and 6M Canadian credit card application records, 140,000 SSNs, 80,000 bank account numbers, credit scores, and transaction history.

Tool: aws s3 sync
Data exfiltrated: 106M records, 140K SSNs, 80K bank accounts, credit/financial history
Detection gap: GuardDuty not enabled; S3 access logs not monitored for volume anomalies
aws s3 syncPIIT1530Financial Data
🚨 Discovery
06
Discovered 77 days later — Thompson bragged about it on GitHub and IRC

Thompson bragged about the breach on GitHub and in Slack and IRC channels under the handle "erratic." A member of the public noticed the posts, reviewed the data, and filed a responsible disclosure with Capital One on July 17, 2019. No internal monitoring — not GuardDuty, not S3 access logs, not IAM anomaly detection — caught the breach during the 77-day dwell period.

Discovery method: External tipster via Capital One responsible disclosure program
Monitoring failures: No GuardDuty · No S3 volume alerts · No anomalous IAM activity detection
Arrest: Paige Thompson, July 29, 2019
77 Day DwellNo Internal DetectionExternal Tipster

🛡 How to Defend Against This Chain

Enforce IMDSv2 on all EC2 instances. IMDSv2 requires a session token obtained via a PUT request — SSRF attacks that can only issue GET requests cannot obtain credentials. AWS defaults new instances to IMDSv2 and you can enforce it via SCP across your organisation.
Apply least-privilege IAM to every role. A WAF role should only write WAF logs — not list or read S3. Use IAM Access Analyzer findings and Access Advisor unused-permission reports to identify and tighten over-privileged roles.
Enable Amazon GuardDuty. GuardDuty's UnauthorizedAccess:IAMUser/InstanceCredentialExfiltration finding specifically detects role credentials being used from outside the EC2 instance that issued them.
Alert on anomalous S3 access volume. Large-scale GetObject or ListBucket calls from an unexpected IP or at an unusual rate should trigger a CloudWatch + SNS alert immediately.
Add SSRF rules to your WAF. Block requests containing 169.254.169.254 in URL, body, or headers. This is not in default WAF configurations — it must be explicitly added.