Get the Zoom link
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 117 days and directly caused AWS to build IMDSv2.

106M records exposed
117 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 and 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 was 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, in violation of 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
Reported 117 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 caught the breach during the 117-day dwell period: not GuardDuty, not S3 access logs, not IAM anomaly detection.

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
117 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