threatfound
Cloud Security
Cloud Security · lesson 03

The metadata service & SSRF-to-credentials

How a server-side request forgery flaw becomes full cloud account access by tricking a vulnerable app into reading temporary IAM credentials from the instance metadata service, and how IMDSv2 and least privilege break the chain.

9 min read

Every cloud instance can reach a special link-local address, 169.254.169.254, that answers HTTP requests with data about itself: its configuration, its user-data script, and, critically, the temporary IAM role credentials the instance uses to call cloud APIs. This is the instance metadata service (IMDS). It is meant to be read only from inside the machine, so it carries no password. That design is safe right up until an application on the instance can be tricked into making requests on an attacker's behalf, at which point the metadata service becomes a credential vending machine.

What the metadata endpoint exposes

The metadata service serves plain HTTP, with no TLS and no authentication, because it assumes only local software talks to it. Useful paths include the instance identity, the user-data script (which frequently contains bootstrap secrets), and the IAM credentials path. Reading the role name and then its credentials returns an AccessKeyId, a SecretAccessKey, and a session Token — everything needed to sign API calls as that role until the short expiry. Those three values are the prize.

# IMDSv1: a plain GET, no auth, no headers required
curl http://169.254.169.254/latest/meta-data/iam/security-credentials/
# -> web-app-role

curl http://169.254.169.254/latest/meta-data/iam/security-credentials/web-app-role
# -> {
#   "AccessKeyId": "ASIA...",
#   "SecretAccessKey": "...",
#   "Token": "...",
#   "Expiration": "2026-07-25T18:00:00Z"
# }
careful

Requesting the metadata endpoint on an instance you do not own, or extracting credentials through someone else's SSRF flaw, is unauthorized access to their cloud account. Only probe the metadata service or test SSRF against systems you own or have explicit written authorization to test.

SSRF: the server makes the request for you

Server-side request forgery (SSRF) is a flaw where an application fetches a URL the attacker controls — an image proxy, a webhook tester, a PDF renderer, a link-preview generator. Normally that fetch reaches the public internet. Because the metadata IP is reachable only from inside the instance, an external attacker cannot hit it directly, but a vulnerable server can — so the attacker supplies the metadata URL and lets the server read the credentials and return them in the response. This SSRF -> metadata -> IAM chain is the mechanism behind several large real-world cloud breaches. See the SSRF lesson for the input-validation side of the bug.

# App feature: server-side fetch of a user-supplied "url" (image proxy / webhook tester)
GET /fetch?url=https://example.com/logo.png

# Attacker swaps the target for the metadata IP:
GET /fetch?url=http://169.254.169.254/latest/meta-data/iam/security-credentials/web-app-role

# The server issues the request from inside the instance and returns the
# credential JSON in its response body. The attacker now signs API calls as web-app-role.

IMDSv1 vs IMDSv2: why the version matters

The original protocol, IMDSv1, answers any GET request. That is exactly what most SSRF hands an attacker: a single GET to a URL of their choosing, so v1 gives up credentials with zero friction. IMDSv2 adds a lightweight handshake. The client must first obtain a session token with a PUT request that sets a custom header, then include that token header on every data request. A naive SSRF that can only issue GETs — and cannot change the HTTP method or set arbitrary request headers — cannot complete the handshake, which blocks the most common exploitation path.

# IMDSv2 requires a session token, obtained with a PUT plus a custom header:
TOKEN=$(curl -s -X PUT "http://169.254.169.254/latest/api/token" \
  -H "X-aws-ec2-metadata-token-ttl-seconds: 21600")

# Every data request must then carry the token header:
curl -s -H "X-aws-ec2-metadata-token: $TOKEN" \
  http://169.254.169.254/latest/meta-data/iam/security-credentials/

# A GET-only SSRF cannot do the PUT or set that header, so the handshake fails.
tip

Enforce IMDSv2 as *required*, not merely *available*. If the endpoint still answers plain v1 GETs, an SSRF ignores v2 entirely. On AWS, set --http-tokens required so v1 is refused, and set --http-put-response-hop-limit 1 so a container or proxy one network hop away from the host cannot reach the metadata service.

How to prevent it

  • Enforce IMDSv2 across the fleet: set metadata options to http-tokens: required so plain v1 GETs are rejected, and pin http-put-response-hop-limit: 1 so pods and sidecars a hop away cannot reach IMDS.
  • Block egress to `169.254.169.254` from application containers and proxies with a host firewall or network policy, so even a v1-style request never leaves the app layer.
  • Allow-list URLs in any server-side fetch: resolve the hostname, reject link-local and private ranges (169.254.0.0/16, 127.0.0.0/8, RFC1918), and re-check after every redirect — validating the raw string alone is trivially bypassable. This is the core SSRF defense.
  • Apply least privilege to instance roles so a stolen role can do little. A web server rarely needs iam:* or s3:* on every bucket; scope it to the exact actions and resources it actually uses. See the IAM lesson.
  • Detect and alert: cloud API calls made from an unexpected source IP using an instance role, or a sudden spike in metadata credential fetches, are strong signals that a role has been exfiltrated and used off-box.