Parameter

Instance metadata service (IMDS)

Also known as

  • Cloud metadata endpoint
  • Metadata endpoint

The instance metadata service (IMDS) is a local HTTP endpoint at 169.254.169.254 that a cloud VM queries to learn about itself, including the temporary credentials of its attached role, which makes it the highest-value target for a server-side request forgery attack.

Last reviewed

What is the instance metadata service?

The instance metadata service is a link-local HTTP endpoint, 169.254.169.254, that every major cloud exposes to running virtual machines. A VM queries it to learn its region, its network configuration, its user data, and, most importantly for security, the temporary credentials of the IAM role or service account attached to it. Nothing outside the VM can reach the address; the request never leaves the host.

That last property is exactly why IMDS matters to an attacker. The endpoint hands out live credentials to anything running on the instance that can make an HTTP request, so a server-side request forgery bug that makes the server fetch a URL becomes credential theft the moment it is pointed at the metadata address.

AWS: IMDSv1 vs IMDSv2

AWS offers two versions of the service, and the difference is the whole security story.

IMDSv1 is a plain request/response protocol. One GET returns credentials:

curl http://169.254.169.254/latest/meta-data/iam/security-credentials/
# -> app-ec2-role
curl http://169.254.169.254/latest/meta-data/iam/security-credentials/app-ec2-role
# -> {"AccessKeyId": "...", "SecretAccessKey": "...", "Token": "..."}  valid now

An SSRF that can only produce a GET satisfies IMDSv1 completely. That single unauthenticated request is what made the class of breach possible.

IMDSv2 is session-oriented. The client first sends a PUT to obtain a token, supplying a TTL header, then includes that token on every GET:

TOKEN=$(curl -X PUT "http://169.254.169.254/latest/api/token" \
  -H "X-aws-ec2-metadata-token-ttl-seconds: 21600")

curl -H "X-aws-ec2-metadata-token: $TOKEN" \
  http://169.254.169.254/latest/meta-data/iam/security-credentials/

The TTL can be from 1 second to 6 hours (21,600 seconds). When token usage is set to required, a GET without a valid token gets 401 Unauthorized. Three properties break the common SSRF:

  • The initial request is a PUT, and many SSRF-prone HTTP clients only issue GET.
  • The token must travel in a request header, which a plain URL-fetch primitive cannot add.
  • PUT requests are rejected if they carry an X-Forwarded-For header, which blocks a class of reverse-proxy relays.

The other defense is the hop limit. By default the response to a PUT has an IP-level hop limit of 1, so a token request that passes through a container network hop or an on-host proxy is dropped before it returns. You can raise HttpPutResponseHopLimit for legitimate container workloads, but raising it too far re-widens the attack surface.

GCP and Azure

The other clouds gate their metadata endpoints with a required header. There is no token session. An SSRF that can only set the URL, not headers, cannot satisfy them; one that can influence headers sometimes can.

Google Cloud requires Metadata-Flavor: Google on every request. The endpoint returns service account access tokens:

curl -H "Metadata-Flavor: Google" \
  http://metadata.google.internal/computeMetadata/v1/instance/service-accounts/default/token

The header value is checked exactly, and GCP rejects requests that arrive with an X-Forwarded-For header, which blunts proxy-based SSRF.

Azure requires the header Metadata: true and a mandatory api-version query parameter. Its managed-identity token endpoint:

curl -s -H "Metadata: true" --noproxy "*" \
  "http://169.254.169.254/metadata/identity/oauth2/token?api-version=2018-02-01&resource=https://management.azure.com/"

A request missing the header or the api-version gets an error and no data.

Enforcing token-required in Terraform

The most reliable fix is to require IMDSv2 at provision time so no instance ever exposes the unauthenticated path. On AWS, set http_tokens = "required" in the metadata_options block, and keep the hop limit at 1 unless a container runtime genuinely needs more:

resource "aws_instance" "app" {
  ami           = "ami-0abcdef1234567890"
  instance_type = "t3.small"

  metadata_options {
    http_endpoint               = "enabled"
    http_tokens                 = "required" # rejects IMDSv1 GETs
    http_put_response_hop_limit = 1          # blocks off-host / extra-hop token requests
  }
}

For a launch template used by an Auto Scaling group, set the same block under metadata_options in aws_launch_template, so instances inherit it as they scale. Enforce it organization-wide with an SCP or the account-level default that sets IMDSv2 as the default for new instances, and audit existing hosts, since flipping the default does not retrofit running instances.

Requiring IMDSv2 is not a substitute for fixing the SSRF that reaches the endpoint. It is defense in depth: the app-layer bug should be fixed, and the metadata service hardened so the bug does not automatically become credential theft. Blocking egress to 169.254.169.254 from workloads that never legitimately call it closes the path entirely.

Written by Parameter · Last reviewed