Last updated: April 26, 2026
One missing input filter on a server-side request lets an attacker reach http://169.254.169.254/ from your EC2 instance. From that single curl, the attacker can extract IAM role credentials, pivot to S3, dump customer data, and in some cases reach the AWS Organization root. This article walks the full SSRF-to-cloud-compromise chain, the detection that catches it, and the IMDSv2 / hop-limit configuration that closes it.
The attack in 60 seconds
Every EC2 instance has access to the EC2 Instance Metadata Service at 169.254.169.254. By default, the IAM role attached to the instance is exposed at /latest/meta-data/iam/security-credentials/<role-name>. Curl that endpoint and you get temporary AWS credentials.
If your application has SSRF — anywhere a user-controlled URL is fetched server-side — the attacker can reach the metadata endpoint through your application:
# Vulnerable app: GET /fetch?url=https://api.example.com/data
GET /fetch?url=http://169.254.169.254/latest/meta-data/iam/security-credentials/MyAppRole
# Response includes:
{
"AccessKeyId": "ASIA...",
"SecretAccessKey": "...",
"Token": "FwoGZX...",
"Expiration": "2026-04-26T18:30:00Z"
}
From here the attacker configures these credentials and assumes the role. Whatever MyAppRole can do in AWS, they can now do.
The CloudGoat scenario, in real life
A Series-B Indian fintech we audited had an “image proxy” microservice — its job was to download user-provided images, resize them, and store them. The URL parameter was not validated. Their SSRF-to-cloud chain:
- 0:00 — submit POST with
url=http://169.254.169.254/latest/meta-data/iam/security-credentials/ - 0:01 — get back the role name (e.g.
fintech-imageproxy-role) - 0:02 — submit POST with
url=http://169.254.169.254/latest/meta-data/iam/security-credentials/fintech-imageproxy-role - 0:03 — receive temporary IAM credentials valid for 6 hours
- 0:05 — configure aws-cli with these credentials, run
aws s3 ls - 0:06 — discover S3 bucket containing customer KYC documents
- 0:08 —
aws s3 sync s3://customer-kyc-bucket/ ./local/ - 0:30 — 240 GB of KYC data extracted before egress controls flag it
The role had been over-permissioned six months earlier when an engineer “needed to read the bucket for testing.” The temporary fix was never rolled back.
How the metadata endpoint became this dangerous
EC2 IMDS was designed in 2007 as a way for an instance to discover its own configuration. The endpoint was available at a non-routable IP, on the assumption that only the instance itself could reach it. Then SSRF became the most common web vulnerability of the 2010s, and “only the instance itself” turned out to include “anyone who can make the instance fetch a URL on their behalf.”
AWS responded with IMDSv2 in November 2019 — a session-token-based version of the same service that defeats most SSRF chains. But IMDSv1 remains supported, and an enormous number of running instances still have it enabled.
The IMDSv2 fix
IMDSv2 requires a PUT request first to obtain a session token, then a GET with that token in a header. SSRF-able applications usually only support GET — so the PUT step itself is often a roadblock.
# IMDSv2 flow (from a normal client on the instance):
TOKEN=$(curl -X PUT "http://169.254.169.254/latest/api/token" \
-H "X-aws-ec2-metadata-token-ttl-seconds: 21600")
curl -H "X-aws-ec2-metadata-token: $TOKEN" \
http://169.254.169.254/latest/meta-data/iam/security-credentials/
Most application SSRFs cannot send PUT requests, cannot inject custom headers in the way IMDSv2 requires. Forcing IMDSv2 closes the SSRF-to-credentials path for ~95% of vulnerable applications.
To enforce on existing instances:
aws ec2 modify-instance-metadata-options \
--instance-id i-XXX \
--http-tokens required \
--http-endpoint enabled \
--http-put-response-hop-limit 1
The --http-put-response-hop-limit 1 is critical for containerised workloads — it prevents containers two hops away (i.e. inside a Docker container on the EC2 host) from reaching the metadata endpoint via PUT.
To enforce at the organisation level via Service Control Policy:
{
"Version": "2012-10-17",
"Statement": [{
"Effect": "Deny",
"Action": "ec2:RunInstances",
"Resource": "arn:aws:ec2:*:*:instance/*",
"Condition": {
"StringNotEquals": {
"ec2:MetadataHttpTokens": "required"
}
}
}]
}
Detection — what works
Application-side, the SSRF itself should be detected by:
- WAF rules that block requests with
169.254.169.254,localhost,metadata.google.internal, orfd00:ec2::254in URL parameters, headers, or body. AWS WAF, Cloudflare WAF, ModSecurity all support this. - Egress controls on the VPC — block outbound to 169.254.169.254 from application subnets. The metadata endpoint is host-local; if the application is calling it, that is anomalous.
Cloud-side, the credential-misuse should be detected by:
- GuardDuty UnauthorizedAccess:IAMUser/InstanceCredentialExfiltration finding — fires when EC2 instance credentials are used from outside that instance.
- CloudTrail anomaly detection — sudden volume of S3 List/Get from an EC2 role outside its normal pattern.
- VPC Flow Logs — egress to 169.254.169.254 from application IPs, especially with PUT method (IMDSv2 attempts) from unusual sources.
How to find your next SSRF
For attackers:
- Look for any place an application accepts a URL — image fetchers, webhook configs, profile-picture imports, RSS feed processors, OAuth callback handlers, PDF generators.
- Test
http://169.254.169.254/,http://[fd00:ec2::254]/(IPv6 metadata),http://metadata.google.internal/(GCP),http://169.254.169.254/metadata/v1/(Azure-style). - If IMDSv1 is blocked, try DNS rebinding (resolves to a public IP first, then 169.254.169.254 on retry). Some application HTTP libraries follow the second resolution.
- Check for
http://localhostSSRF — applications running admin endpoints on localhost are often reachable through SSRF too.
For defenders:
- Audit instance metadata options across the fleet:
aws ec2 describe-instances --query "Reservations[].Instances[].[InstanceId,MetadataOptions.HttpTokens]". Anyoptionalentry is IMDSv1-enabled and a target. - Enforce IMDSv2 via SCP at the organisation level. Make new launches default to required.
- Apply hop-limit 1 to every instance — prevents container-escape SSRF.
- Tighten EC2 instance roles using IAM Access Analyzer. The role should have least-privilege; if it has S3 read across all buckets, narrow that down.
Compliance angle
- DPDP §8(5) — over-privileged EC2 roles processing personal data with IMDSv1 enabled is a defensible-security failure.
- RBI Cloud Guidelines — payments-data workloads on EC2 must enforce IMDSv2; this is increasingly an inspection question.
- SEBI CSCRF — cloud workloads carrying investor data require strong identity hygiene including IMDSv2.
- ISO 27017 A.10 — encryption and identity controls for cloud services.
The takeaway
SSRF-to-cloud-compromise via the EC2 metadata service is the single highest-impact vulnerability class in cloud-native applications. The fix is mechanical: enforce IMDSv2 with hop-limit 1, scope instance roles to least privilege, monitor for credential exfiltration. The work is one engineering sprint per environment. The cost of skipping it is measured in customer data breaches, DPDP penalties, and headline risk. Run describe-instances against your own fleet today; whatever percentage shows IMDSv1-enabled is your target reduction for the quarter.
Get a VAPT scoping call
Senior practitioner-led VAPT — not a checklist run by juniors. CVSS-scored findings, free retest, attestation letter. India's SMBs and SaaS teams.