Retrieving EC2 Instance ID Safely: IMDSv2 vs IMDSv1 Explained

When writing automation scripts or bootstrap logic on an EC2 instance, you often need the instance's own ID — for tagging, logging, or calling AWS APIs. The built-in Instance Metadata Service (IMDS) is the canonical way to get it, but how you query it determines whether your instance is a security liability or not.

TL;DR

AspectIMDSv1 (Legacy)IMDSv2 (Recommended)
Auth ModelNone — open GET requestSession-oriented token required
SSRF VulnerabilityFully exploitableMitigated by token requirement
Request Flow1 step (GET metadata)2 steps (PUT token → GET metadata)
AWS RecommendationDeprecated for new workloads✅ Enforce via instance config
Core TakeawayAlways use IMDSv2. Enforce it at launch with --metadata-options HttpTokens=required.

Why IMDS Exists

Every EC2 instance has access to a special link-local IP address — 169.254.169.254 — that serves instance-specific metadata: instance ID, AMI ID, IAM role credentials, region, and more. This endpoint is only reachable from within the instance itself (it is not routable over the internet), making it the authoritative self-discovery mechanism for any script running on the host.

The endpoint path for the instance ID is:

http://169.254.169.254/latest/meta-data/instance-id

The Security Problem with IMDSv1

IMDSv1 is a simple, stateless HTTP GET. No authentication. No session. Any process — or any HTTP request that can be proxied through the instance — can retrieve sensitive metadata, including temporary IAM credentials attached to the instance profile.

Analogy: IMDSv1 is like a building's master key cabinet with no lock. Anyone who walks into the lobby — even someone who snuck in through a side door — can grab the keys. IMDSv2 adds a locked cabinet: you must first prove you're physically inside the building (the instance) to get a time-limited key, and only then can you open the cabinet.

The "side door" in this analogy is a Server-Side Request Forgery (SSRF) attack. If your application has an SSRF vulnerability (e.g., a URL-fetching feature that an attacker can manipulate), the attacker can instruct your server to fetch http://169.254.169.254/latest/meta-data/iam/security-credentials/<role-name> and return the result — handing over live AWS credentials.

How IMDSv2 Closes the Gap

IMDSv2 introduces a session-oriented, token-based flow. The key defense is that the token request uses an HTTP PUT method with a custom header (X-aws-ec2-metadata-token-ttl-seconds). Most SSRF vulnerabilities exploit simple GET requests; proxying a PUT with custom headers is significantly harder and blocked by many web frameworks and proxies by default.

sequenceDiagram participant Script as 'Script on EC2' participant IMDS as 'IMDS Endpoint (169.254.169.254)' Note over Script,IMDS: Step 1 — Acquire Session Token (PUT) Script->>IMDS: PUT /latest/api/token
Header: X-aws-ec2-metadata-token-ttl-seconds: 21600 IMDS-->>Script: Returns TOKEN string Note over Script,IMDS: Step 2 — Query Metadata (GET + Token) Script->>IMDS: GET /latest/meta-data/instance-id
Header: X-aws-ec2-metadata-token: TOKEN IMDS-->>Script: i-0abc1234def56789

The token is valid only for the TTL you specify (in seconds) and is bound to the instance. An attacker exploiting SSRF cannot easily replicate the PUT + custom header combination through a naive proxy.

Implementation: Retrieving Instance ID with IMDSv2

Bash (Shell Script)

#!/bin/bash
# Step 1: Get a session token (TTL = 6 hours)
TOKEN=$(curl -s -X PUT "http://169.254.169.254/latest/api/token" \
  -H "X-aws-ec2-metadata-token-ttl-seconds: 21600")

# Step 2: Use the token to fetch the instance ID
INSTANCE_ID=$(curl -s -H "X-aws-ec2-metadata-token: $TOKEN" \
  http://169.254.169.254/latest/meta-data/instance-id)

echo "Instance ID: $INSTANCE_ID"

Python (boto3-free, using requests)

💻 [Click to expand] Python: IMDSv2 Instance ID Retrieval
import requests

IMDS_BASE = "http://169.254.169.254"

def get_imdsv2_token(ttl_seconds: int = 21600) -> str:
    """Acquire a session token from IMDSv2."""
    response = requests.put(
        f"{IMDS_BASE}/latest/api/token",
        headers={"X-aws-ec2-metadata-token-ttl-seconds": str(ttl_seconds)},
        timeout=2
    )
    response.raise_for_status()
    return response.text

def get_instance_id() -> str:
    """Retrieve the EC2 instance ID using IMDSv2."""
    token = get_imdsv2_token()
    response = requests.get(
        f"{IMDS_BASE}/latest/meta-data/instance-id",
        headers={"X-aws-ec2-metadata-token": token},
        timeout=2
    )
    response.raise_for_status()
    return response.text

if __name__ == "__main__":
    print(f"Instance ID: {get_instance_id()}")

Enforcing IMDSv2 at the Infrastructure Level

Relying on scripts to "use the right version" is fragile. The correct approach is to disable IMDSv1 entirely at instance launch, so no code path — intentional or accidental — can fall back to the insecure version.

AWS CLI — At Launch

aws ec2 run-instances \
  --image-id ami-0abcdef1234567890 \
  --instance-type t3.micro \
  --metadata-options HttpTokens=required,HttpEndpoint=enabled

Enforce on an Existing Instance

aws ec2 modify-instance-metadata-options \
  --instance-id i-0abc1234def56789 \
  --http-tokens required \
  --http-endpoint enabled

Terraform

🛠️ [Click to expand] Terraform: EC2 with IMDSv2 Enforced
resource "aws_instance" "app_server" {
  ami           = "ami-0abcdef1234567890"
  instance_type = "t3.micro"

  metadata_options {
    http_endpoint               = "enabled"
    http_tokens                 = "required"   # Enforces IMDSv2
    http_put_response_hop_limit = 1             # Prevents container-escape attacks
  }

  tags = {
    Name = "app-server"
  }
}

Note on hop limit: Setting http_put_response_hop_limit = 1 ensures the token PUT request cannot be forwarded beyond the instance itself — a critical defense when running containers on EC2, preventing a compromised container from reaching the host metadata endpoint.

Architecture: Request Flow Comparison

IMDSv1 (Insecure)IMDSv2 (Secure)Attacker via SSRFGET /meta-data/instance-idIMDS EndpointScript on EC2PUT /api/token (TTL header)Session TokenGET /meta-data/instance-id + TokenIMDS Endpoint No auth neededReturns credentials freelyStep 1Token issuedStep 2Authenticated requesti-0abc1234def56789

IAM & Security Notes

  • IMDS itself requires no IAM permissions — it is a local HTTP endpoint, not an AWS API call.
  • However, the IAM credentials returned by IMDS (for the attached instance profile) are what attackers target. Enforcing IMDSv2 protects those credentials from SSRF exfiltration.
  • Use AWS Config Rule ec2-imdsv2-check to audit all instances in your account for IMDSv1 exposure at scale.

Wrap-up & Next Steps

IMDSv2's session-token model is a simple, zero-cost upgrade that eliminates an entire class of credential-theft attacks — enforce HttpTokens=required on every instance and never rely on IMDSv1 again.

Next Steps:

  • 📖 AWS Docs: Configure Instance Metadata Service
  • Run aws ec2 describe-instances --query 'Reservations[].Instances[].{ID:InstanceId,IMDSv2:MetadataOptions.HttpTokens}' to audit your fleet today.
  • Enable the AWS Config managed rule ec2-imdsv2-check for continuous compliance monitoring.

Glossary

  • IMDS (Instance Metadata Service): A local HTTP endpoint (169.254.169.254) on every EC2 instance that serves instance-specific configuration and credentials without requiring AWS API calls.
  • IMDSv2: The session-oriented version of IMDS that requires a PUT-based token acquisition step before any metadata can be read.
  • SSRF (Server-Side Request Forgery): An attack where a malicious actor tricks a server into making HTTP requests to internal endpoints (like IMDS) on their behalf.
  • Link-local address: An IP address in the 169.254.0.0/16 range that is only reachable within a single network segment — never routed over the internet.
  • Instance Profile: An IAM role attached to an EC2 instance, whose temporary credentials are accessible via IMDS and are the primary target of SSRF-based metadata attacks.

Comments

Popular posts from this blog

EC2 No Internet Access in Custom VPC: Attaching an Internet Gateway and Fixing Route Tables

Lambda Infinite Loop with S3: How to Break the Recursive Trigger Cycle

IAM User vs. IAM Role: Why Your EC2 Instance Should Never Use a User