Block SSRF to Cloud Metadata with Token-Enforced IMDS Access
For developers running workloads on cloud VMs or containers, this guide explains why SSRF and instance metadata are such a dangerous combination and how token-enforced metadata access changes the attack path. You’ll get a concrete mental model, a realistic end-to-end example, and practical commands and config patterns you can apply when hardening apps and hosts.
TL;DR — SSRF gets much worse when your app can reach the instance metadata service, because metadata often exposes temporary credentials. The highest-value fix on platforms that support it is to require session tokens for metadata access and then verify your app, agents, and bootstrap scripts still work without falling back to unauthenticated metadata calls. Reading time: ~7 min
What it is and where it sits
The interesting part is not “metadata exists”; it’s where it sits in the trust boundary.
The instance metadata service (IMDS) is a link-local HTTP endpoint exposed to workloads running on a VM. It usually lives at a non-routable address such as 169.254.169.254, and it returns information about the current instance: identity documents, region/zone, network config, and often credentials for the attached machine or workload role.
That endpoint replaced older patterns like baking long-lived API keys into images, config files, or environment variables. Operationally, that is a huge improvement: credentials rotate automatically, and apps can discover local instance context without external calls.
Architecturally, IMDS sits next to your application, not behind your normal perimeter controls. A browser cannot hit it directly from the public internet, but any process that can make outbound HTTP from the instance often can. That is why SSRF matters here: an attacker does not need shell access if they can trick your app into fetching arbitrary URLs.
Typical flow:
- your app receives a user-controlled URL or host
- the app server fetches it
- the fetcher can reach
169.254.169.254 - metadata returns credentials or identity data
- attacker uses those credentials against cloud APIs
Token-enforced metadata access changes one key assumption: simple unauthenticated GET requests to IMDS stop working. The caller must first obtain a short-lived token from IMDS using a specific HTTP method and headers, then present that token on subsequent metadata requests. That does not make SSRF impossible in all cases, but it blocks a large class of “blind fetch this URL” bugs and many misconfigured proxies/WAF bypasses.
View as text
[Attacker] ---- 1. URL input ----> [Vulnerable App] [Vulnerable App] ---- 2. server-side fetch ----> [Target URL] [Vulnerable App] ---- 3. SSRF to 169.254.169.254 ----> [IMDS] [IMDS] ---- 4. creds / identity ----> [Vulnerable App] [Vulnerable App] ---- 5. leaked response ----> [Attacker] [Attacker] ---- 6. cloud API calls ----> [Cloud Control Plane]
Where it lives in request flow terms: it is usually reachable only from the host network namespace or from containers bridged onto it. That means your reverse proxy, app runtime, package managers, sidecars, and agents may all be potential callers. If you run containers, whether they can reach IMDS depends on your network mode, iptables rules, CNI behavior, and hop-limit settings.
How it actually works
Take one realistic example: an image-processing API accepts a user-supplied URL and downloads it server-side.
The vulnerable code path is effectively:
POST /thumbnail
{"url":"http://169.254.169.254/latest/meta-data/iam/security-credentials/"}
Without token enforcement, many fetchers will happily do this.
A direct test from the instance often looks like this:
curl -sS http://169.254.169.254/latest/meta-data/
Typical output shape:
ami-id
hostname
iam/
instance-id
local-ipv4
placement/
security-groups
And credentials enumeration often looks like:
curl -sS http://169.254.169.254/latest/meta-data/iam/security-credentials/
Output shape:
my-instance-role
Then:
curl -sS http://169.254.169.254/latest/meta-data/iam/security-credentials/my-instance-role
Output shape:
{
"Code" : "Success",
"LastUpdated" : "2026-10-30T10:12:15Z",
"Type" : "AWS-HMAC",
"AccessKeyId" : "ASIA...",
"SecretAccessKey" : "...",
"Token" : "...",
"Expiration" : "2026-10-30T16:31:42Z"
}
That is the failure mode token enforcement is trying to break.
With token-enforced IMDS, the caller must do two steps.
Step 1: request a token with PUT and a TTL header.
curl -sS -X PUT "http://169.254.169.254/latest/api/token" \
-H "X-aws-ec2-metadata-token-ttl-seconds: 21600"
Output shape:
AQAEAHh7...<opaque token>...==
Step 2: include that token in the metadata request.
TOKEN=$(curl -sS -X PUT "http://169.254.169.254/latest/api/token" \
-H "X-aws-ec2-metadata-token-ttl-seconds: 21600")
curl -sS "http://169.254.169.254/latest/meta-data/iam/security-credentials/" \
-H "X-aws-ec2-metadata-token: $TOKEN"
If token enforcement is enabled and you forget the token, the failure is obvious:
curl -i http://169.254.169.254/latest/meta-data/
Typical output shape:
HTTP/1.1 401 Unauthorized
Content-Type: text/plain
Content-Length: 0
Date: Fri, 30 Oct 2026 10:15:02 GMT
Server: EC2ws
Connection: close
That changes the SSRF game in a few concrete ways:
- A simple “fetch this URL and return body” bug often cannot perform the initial
PUTwith custom headers. - Middleware that only permits
GETandHEADstops being enough to reach IMDS. - Open proxies and redirect-based SSRF chains often fail because the attacker cannot inject the required metadata token header.
A useful diagnostic edge case: redirects. If your fetcher follows redirects and you are checking whether it can be bounced into IMDS, curl -I -L often reveals behavior quickly.
curl -I -L https://example.test/redirect-to-metadata
Typical output shape on a dangerous fetch path:
HTTP/1.1 302 Found
Location: http://169.254.169.254/latest/meta-data/
HTTP/1.1 401 Unauthorized
Content-Type: text/plain
Content-Length: 0
That 401 is good news if token enforcement is on. If you instead see 200 OK and metadata keys, your path is still exploitable.
When to use it (and when not to)
Use token-enforced metadata access anywhere workloads can reach IMDS and you rely on instance-attached credentials or identity documents.
| Scenario | Recommendation |
|---|---|
| VM instances with attached roles and any app that fetches user-supplied URLs | Enable token-enforced IMDS immediately; then test app libraries, agents, and bootstrap scripts |
| Container workloads on VMs where pods/containers can reach host metadata | Enable token enforcement and also add network blocking to 169.254.169.254 for workloads that do not need it |
Legacy scripts using plain curl http://169.254.169.254/... in init or deploy hooks | Migrate scripts first or stage rollout; token enforcement will break them |
| Workloads using a provider-native workload identity mechanism that avoids instance metadata for app credentials | You probably still want token enforcement for host-level safety, but app-level risk is lower |
| On-prem VMs with no cloud metadata endpoint | You do not need this specific control; focus on SSRF egress restrictions and internal service allowlists |
| Apps with no server-side fetch capability and no untrusted URL handling | This is still worth enabling at the platform layer, but it is not your first SSRF control |
You probably do not need to spend much time on this if your architecture never exposes metadata to application workloads and uses a dedicated workload identity path that is isolated from app network access. But that is rarer than teams think; many “just a helper sidecar” or “temporary bootstrap script” paths still hit IMDS.
Trade-offs
Every benefit here comes with operational cost.
-
Benefit: blocks a broad class of trivial SSRF-to-metadata attacks.
Cost: older SDKs, cloud-init fragments, shell scripts, and third-party agents may fail until updated. -
Benefit: reduces blast radius from URL fetch bugs and open redirect chains.
Cost: rollout requires inventory work: find everything on the host that calls169.254.169.254. -
Benefit: encourages modern SDK usage instead of ad hoc metadata scraping.
Cost: some teams lose “simple curl debugging” unless they learn the token flow. -
Benefit: can be enforced at the instance/platform layer, not per app.
Cost: platform-level changes can break many apps at once if you flip the setting globally without testing. -
Benefit: no direct latency cost to normal app traffic.
Cost: metadata consumers add one extra token request per token lifetime; usually negligible, but noticeable in very chatty homemade clients. -
Benefit: better defense in depth.
Cost: not a complete SSRF fix. If the attacker can force arbitrary methods and headers, or gets code execution, token enforcement alone will not save you.
The lock-in question is real: token-enforced metadata access is provider-specific in details, even if the security idea is generic. The portable architectural lesson is not “use this exact header forever”; it is “do not let application traffic directly consume unauthenticated host metadata.”
In practice
Example 1: verify token enforcement from a host
set -euo pipefail
IMDS=http://169.254.169.254
echo "1) Unauthenticated request should fail"
code=$(curl -s -o /dev/null -w "%{http_code}" "$IMDS/latest/meta-data/")
echo "HTTP $code"
echo "2) Request token"
TOKEN=$(curl -sS -X PUT "$IMDS/latest/api/token" \
-H "X-aws-ec2-metadata-token-ttl-seconds: 300")
echo "Token length: ${#TOKEN}"
echo "3) Authenticated metadata request should succeed"
curl -sS "$IMDS/latest/meta-data/instance-id" \
-H "X-aws-ec2-metadata-token: $TOKEN"
echo
This is the fastest smoke test after enabling token enforcement. The gotcha: if step 1 returns 200 instead of 401, token enforcement is not active on that instance or you are not testing the real metadata endpoint from the real network namespace.
Example 2: block container access to IMDS with iptables
⚠️ This changes host firewall behavior and can break agents, bootstrap jobs, or containers that legitimately depend on metadata. Apply first on a non-production node and verify node recovery access before rollout.
iptables -I DOCKER-USER 1 -d 169.254.169.254/32 -j REJECT
iptables -I OUTPUT 1 -d 169.254.169.254/32 -m owner ! --uid-owner root -j REJECT
iptables -S DOCKER-USER
iptables -S OUTPUT
This adds a coarse network control so containers and non-root host processes cannot reach IMDS even if SSRF exists. The gotcha: exact chains vary by runtime and distro; on Kubernetes with a CNI plugin, traffic may bypass DOCKER-USER, so validate with a test pod instead of assuming the rule is effective.
Example 3: application-side SSRF guardrail with explicit allowlist
location /fetch-image {
proxy_pass_request_body on;
proxy_set_header Content-Type application/json;
proxy_pass http://image-fetcher;
}
{
"allowed_schemes": ["https"],
"deny_cidrs": [
"127.0.0.0/8",
"10.0.0.0/8",
"172.16.0.0/12",
"192.168.0.0/16",
"169.254.0.0/16",
"::1/128",
"fc00::/7",
"fe80::/10"
],
"max_redirects": 0,
"resolve_and_connect_same_ip": true
}
This is the app-layer control you should pair with token enforcement: deny link-local and private ranges, disable redirects unless required, and pin the resolved IP to the one you connect to. The gotcha: DNS rebinding defeats naive “validate hostname then connect later” implementations; validate the final resolved address and connect to that exact address.
Further reading
- AWS EC2 User Guide — “Use the Instance Metadata Service to access instance metadata” and “Configure the Instance Metadata Service options”
- OWASP SSRF Prevention Cheat Sheet
- AWS Security Blog — posts on IMDSv2 and SSRF defense in depth
- curl man page —
-X,-H,-i,-L, and-w - MDN HTTP docs — the “Redirections in HTTP” article
This article was written by an AI system and published pending human review. Verify anything you intend to act on.
Have a project in mind?
Get an instant AI price estimate for it, or talk directly to our team.
One email a month on what we learn building with AI