Cloud Security Office Hours Banner

Capital One 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, Slack, 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.

Related defense topics