A Server-Side Request Forgery vulnerability in a web application combined with the EC2 Instance Metadata Service creates one of the most commonly exploited attack chains in AWS environments. IMDSv2 closes this chain — understanding how reveals why the fix works.
Sign in to mark this article as read and track your progress.
The 2019 Capital One breach exposed 106 million credit card applications stored in S3. The attacker — a former AWS engineer — used a misconfigured WAF to perform a Server-Side Request Forgery (SSRF) attack, retrieved IAM credentials from the EC2 Instance Metadata Service (IMDS), and used those credentials to enumerate and download data from hundreds of S3 buckets.
Understanding this attack chain in technical depth is essential for cloud security practitioners because it remains among the most common initial access techniques in AWS-hosted environments.
The EC2 Instance Metadata Service (IMDS) is a local HTTP endpoint available at http://169.254.169.254/latest/meta-data/ from within every EC2 instance. It provides the running instance with information about itself:
| IMDS Path | Returns |
|---|---|
/latest/meta-data/instance-id | EC2 instance ID |
/latest/meta-data/iam/security-credentials/ | Name of attached IAM role |
/latest/meta-data/iam/security-credentials/<ROLE_NAME> | Temporary IAM credentials (AccessKeyId, SecretAccessKey, Token, Expiration) |
/latest/meta-data/placement/region | AWS region |
/latest/user-data | User data script (often contains secrets) |
The credentials returned under /iam/security-credentials/ are identical in capability to the IAM role's permissions. If the role has s3:GetObject on all buckets, the attacker now has that access from any location.
SSRF (Server-Side Request Forgery — CWE-918) is a class of vulnerability where an attacker tricks a server into making HTTP requests to an unintended destination.
Attack sequence:
1. Attacker sends HTTP request to vulnerable web app:
POST /api/v1/fetch-url
{"url": "http://169.254.169.254/latest/meta-data/iam/security-credentials/"}
2. Web app makes request TO IMDS on behalf of attacker
(app runs on EC2; 169.254.169.254 is reachable from inside EC2)
3. IMDS returns role name: "EC2FullAccess-Role"
4. Attacker sends:
POST /api/v1/fetch-url
{"url": "http://169.254.169.254/latest/meta-data/iam/security-credentials/EC2FullAccess-Role"}
5. Web app returns JSON: AccessKeyId, SecretAccessKey, Token
6. Attacker uses these credentials from their laptop:
AWS_ACCESS_KEY_ID=...
AWS_SECRET_ACCESS_KEY=...
AWS_SESSION_TOKEN=...
aws s3 ls → lists all S3 buckets
7. Attacker downloads data until credentials expire (~6 hours)
IMDSv1 required no authentication. Any HTTP GET to http://169.254.169.254/ from within the instance returned data. The SSRF vulnerability in the web application was sufficient — the attacker needed nothing else.
AWS released Instance Metadata Service version 2 (IMDSv2) in November 2019, one month after the Capital One breach became public. IMDSv2 requires a two-step process:
Step 1: Request a session-oriented token
PUT http://169.254.169.254/latest/api/token
X-aws-ec2-metadata-token-ttl-seconds: 21600
Response: a one-time token string (e.g., AQAAAFEMk8...)
Step 2: Use the token in metadata requests
GET http://169.254.169.254/latest/meta-data/iam/security-credentials/
X-aws-ec2-metadata-token: AQAAAFEMk8...
Basic SSRF exploits use GET requests. IMDSv2 requires a PUT request in step 1. HTTP redirects from GET cannot change to PUT (browsers and HTTP libraries do not follow redirects that change the method from GET to PUT). Additionally:
X-aws-ec2-metadata-token-ttl-seconds header is a custom AWS header that web application proxies typically do not forward| Scenario | IMDSv1 | IMDSv2 |
|---|---|---|
| Simple SSRF GET to 169.254.169.254 | Credentials returned immediately | Step 1 (PUT) cannot be completed |
| SSRF through an open HTTP proxy | Credentials returned | Token-less requests rejected |
| Code running inside the EC2 instance | Works | Works (PUT is available from inside the instance) |
Enforce IMDSv2:
aws ec2 modify-instance-metadata-options \
--instance-id i-xxxx \
--http-tokens required \
--http-endpoint enabled
Account-wide SCP to require IMDSv2:
{
"Effect": "Deny",
"Action": "ec2:RunInstances",
"Resource": "arn:aws:ec2:*:*:instance/*",
"Condition": {
"StringNotEquals": {
"ec2:MetadataHttpTokens": "required"
}
}
}
Detect SSRF attempts in application logs: Look for outbound requests to 169.254.169.254 from your web tier. These are never legitimate — a web application should never need to contact the metadata service directly.
Monitor with GuardDuty: The finding type UnauthorizedAccess:IAMUser/InstanceCredentialExfiltration.OutsideAWS fires when credentials issued to an EC2 instance are used from an IP address outside AWS — exactly what happened in the Capital One breach.