EC2 Instance-ID per Metadaten abrufen: IMDSv2 vs. IMDSv1

Du schreibst ein Deployment-Skript, das auf dem EC2-Server selbst läuft, und brauchst die Instance-ID — ohne sie hardzukodieren oder aus externen Quellen zu ziehen. Der Instance Metadata Service (IMDS) liefert genau das. Aber wer IMDSv1 noch verwendet, hat eine stille Sicherheitslücke im System, die in realen SSRF-Angriffen bereits ausgenutzt wurde.

TL;DR: IMDSv2 vs. IMDSv1 beim Abruf der EC2 Instance-ID

MerkmalIMDSv1IMDSv2
AuthentifizierungKeine — direkter GET-RequestSession-Token erforderlich (PUT → GET)
SSRF-SchutzNicht vorhandenToken-Pflicht blockiert einfache SSRF-Angriffe
TTL-KontrolleNicht anwendbarToken-Lebensdauer konfigurierbar (1–21600 Sek.)
AWS-EmpfehlungVeraltet, nicht empfohlenEmpfohlen, erzwingbar per Instance-Konfiguration
AbwärtskompatibilitätIMDSv2 funktioniert auch wenn IMDSv1 deaktiviert ist

Wie der Instance Metadata Service funktioniert

Der IMDS ist ein link-lokaler HTTP-Endpunkt, der ausschließlich von innerhalb der EC2-Instanz erreichbar ist. Die IP-Adresse 169.254.169.254 ist eine link-lokale Adresse gemäß RFC 3927 — sie ist nicht routbar und nicht über das Internet erreichbar. Jede Instanz hat ihren eigenen IMDS-Endpunkt, der Metadaten wie Instance-ID, Region, IAM-Rolle und Netzwerkinformationen bereitstellt.

IMDSv1 funktioniert mit einem einfachen GET-Request ohne jede Authentifizierung. Das klingt harmlos, solange der Request von der Instanz selbst kommt. Das Problem: Bei einem SSRF-Angriff (Server-Side Request Forgery) kann eine verwundbare Anwendung auf der Instanz dazu gebracht werden, diesen Request stellvertretend auszuführen — und gibt dabei IAM-Credentials oder andere sensible Metadaten preis.

IMDSv2 löst das durch ein Session-orientiertes Protokoll. Zuerst wird ein kurzlebiger Token per PUT-Request angefordert. Dieser Token muss bei jedem nachfolgenden Metadaten-Request im Header mitgesendet werden. Ein einfacher SSRF-Angriff, der nur GET-Requests erzeugen kann, scheitert an dieser Hürde.

sequenceDiagram participant Skript as Skript (auf EC2) participant IMDS as IMDS (169.254.169.254) participant Angreifer as Angreifer (SSRF) Skript->>IMDS: PUT /latest/api/token
TTL-Header: 21600s IMDS-->>Skript: Session-Token Skript->>IMDS: GET /latest/meta-data/instance-id
Token-Header gesetzt IMDS-->>Skript: i-1234567890abcdef0 Angreifer->>IMDS: GET /latest/meta-data/instance-id
kein Token (nur GET möglich) IMDS-->>Angreifer: HTTP 401 Unauthorized
  1. PUT /latest/api/token: Die Instanz fordert einen Session-Token an und setzt die gewünschte TTL im Header.
  2. Token-Rückgabe: IMDS antwortet mit einem temporären Token.
  3. GET /latest/meta-data/instance-id: Der eigentliche Metadaten-Request, mit Token im Header.
  4. Instance-ID-Rückgabe: IMDS liefert die Instance-ID zurück.
  5. SSRF-Versuch (IMDSv2): Ein Angreifer, der nur GET erzeugen kann, erhält keinen Token und damit keinen Zugriff.

Instance-ID per IMDSv2 abrufen — die sichere Methode

Der folgende Ablauf ist der empfohlene Weg für Skripte, die auf EC2-Instanzen laufen. Beide Schritte sind erforderlich — ohne gültigen Token liefert der IMDS-Endpunkt bei erzwungenem IMDSv2 einen HTTP 401-Fehler.

Schritt 1: Session-Token anfordern

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

Der Header X-aws-ec2-metadata-token-ttl-seconds legt die Token-Lebensdauer in Sekunden fest. Der Maximalwert beträgt 21600 (6 Stunden). Für kurzlebige Skripte reichen deutlich kleinere Werte.

Schritt 2: Instance-ID mit Token abrufen

INSTANCE_ID=$(curl -s \
  'http://169.254.169.254/latest/meta-data/instance-id' \
  -H "X-aws-ec2-metadata-token: $TOKEN")

echo "Instance-ID: $INSTANCE_ID"

Kompaktes Einzeiler-Skript

INSTANCE_ID=$(curl -s \
  -H "X-aws-ec2-metadata-token: $(curl -s -X PUT \
    'http://169.254.169.254/latest/api/token' \
    -H 'X-aws-ec2-metadata-token-ttl-seconds: 21600')" \
  'http://169.254.169.254/latest/meta-data/instance-id')

echo "Instance-ID: $INSTANCE_ID"

Python-Variante (boto3-unabhängig)

🔽 Python-Skript anzeigen (IMDSv2)
import urllib.request

def get_instance_id():
    # Schritt 1: Token anfordern
    token_url = 'http://169.254.169.254/latest/api/token'
    token_req = urllib.request.Request(
        token_url,
        method='PUT',
        headers={'X-aws-ec2-metadata-token-ttl-seconds': '21600'}
    )
    with urllib.request.urlopen(token_req, timeout=2) as resp:
        token = resp.read().decode('utf-8')

    # Schritt 2: Instance-ID abrufen
    metadata_url = 'http://169.254.169.254/latest/meta-data/instance-id'
    metadata_req = urllib.request.Request(
        metadata_url,
        headers={'X-aws-ec2-metadata-token': token}
    )
    with urllib.request.urlopen(metadata_req, timeout=2) as resp:
        return resp.read().decode('utf-8')

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

IMDSv2 auf Instanz-Ebene erzwingen

Das Skript auf IMDSv2 umzustellen reicht nicht, wenn IMDSv1 auf der Instanz noch aktiv ist. Solange http-tokens auf optional steht, akzeptiert der IMDS weiterhin tokenlose Requests. Das ist der Zustand, in dem viele ältere Instanzen noch laufen — und der Zustand, den Angreifer ausnutzen.

IMDSv1 lässt sich pro Instanz deaktivieren:

aws ec2 modify-instance-metadata-options \
  --instance-id i-1234567890abcdef0 \
  --http-tokens required \
  --region us-east-1

Den aktuellen Status einer Instanz prüfen:

aws ec2 describe-instances \
  --instance-ids i-1234567890abcdef0 \
  --query 'Reservations[*].Instances[*].MetadataOptions' \
  --output table \
  --region us-east-1

Alle Instanzen in einer Region finden, bei denen IMDSv1 noch erlaubt ist — weil http-tokens auf optional steht:

aws ec2 describe-instances \
  --filters 'Name=metadata-options.http-tokens,Values=optional' \
  --query 'Reservations[*].Instances[*].[InstanceId,MetadataOptions.HttpTokens]' \
  --output table \
  --region us-east-1

IMDSv1 deaktivieren ist wie das Schloss an einer Tür, die bisher nur angelehnt war. Die Tür war immer da — sie war nur nie wirklich zu.

Typischer Fehler: Symptom, Fehldiagnose, eigentliche Ursache

Das Skript läuft lokal problemlos, schlägt aber in der CI/CD-Pipeline auf der Instanz fehl. Der Fehler sieht so aus:

curl: (22) The requested URL returned error: 401

Erste Reaktion: Netzwerkproblem, Firewall, falscher Endpunkt. Der IMDS-Endpunkt ist link-lokal und nicht über Security Groups steuerbar — das ist also keine Firewall-Frage.

Die eigentliche Ursache: Die Instanz wurde mit --http-tokens required gestartet oder nachträglich auf IMDSv2 umgestellt. Das Skript verwendet aber noch den alten IMDSv1-Request ohne Token. Der 401-Fehler ist das korrekte Verhalten von IMDSv2 bei fehlendem Token.

Fix: Den PUT-Schritt zur Token-Anforderung ergänzen, wie oben gezeigt. Danach funktioniert der Abruf auch auf Instanzen mit erzwungenem IMDSv2.

graph TD A[Skript startet] --> B{Token im Request?} B -- Nein, IMDSv1-Stil --> C[GET ohne Token-Header] C --> D{http-tokens auf Instanz?} D -- optional --> E[HTTP 200: Instance-ID] D -- required --> F[HTTP 401: Unauthorized] F --> G[Fix: PUT für Token ergänzen] G --> H[GET mit Token-Header] H --> E B -- Ja, IMDSv2-Stil --> H
  1. IMDSv1-Request (kein Token): Skript sendet GET ohne Token-Header.
  2. HTTP 401: Instanz hat http-tokens: required — der Request wird abgelehnt.
  3. IMDSv2-Request (mit Token): PUT für Token, dann GET mit Token-Header.
  4. HTTP 200 + Instance-ID: Erfolgreiche Antwort.

IMDSv2 beim Start neuer Instanzen standardmäßig erzwingen

Für neue Instanzen lässt sich IMDSv2 direkt beim Launch als Pflicht setzen, sodass IMDSv1 von Anfang an nicht verfügbar ist:

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

Über AWS Organizations lässt sich per Service Control Policy (SCP) verhindern, dass Instanzen mit HttpTokens=optional gestartet werden — das ist der skalierbare Weg für größere Umgebungen. Die konkrete SCP-Struktur dafür ist in der AWS-Dokumentation zu SCPs und EC2 beschrieben.

Weitere nützliche Metadaten-Pfade

Wer den Token einmal hat, kann damit weitere Metadaten abrufen — immer mit demselben Token im Header:

# Availability Zone
curl -s \
  -H "X-aws-ec2-metadata-token: $TOKEN" \
  'http://169.254.169.254/latest/meta-data/placement/availability-zone'

# Region (aus der AZ ableiten oder direkt)
curl -s \
  -H "X-aws-ec2-metadata-token: $TOKEN" \
  'http://169.254.169.254/latest/meta-data/placement/region'

# IAM-Rollenname der Instanz
curl -s \
  -H "X-aws-ec2-metadata-token: $TOKEN" \
  'http://169.254.169.254/latest/meta-data/iam/security-credentials/'

Den Pfad /latest/meta-data/iam/security-credentials/ liefert nur den Rollennamen. Die eigentlichen temporären Credentials liegen unter /latest/meta-data/iam/security-credentials/ROLLENNAME — das ist genau der Pfad, den ein SSRF-Angriff über IMDSv1 kompromittieren kann.

Wrap-up: EC2 Instance-ID sicher abrufen mit IMDSv2

IMDSv2 ist kein optionales Upgrade — es ist die einzige vertretbare Methode für Produktionsumgebungen. Der Mehraufwand gegenüber IMDSv1 besteht aus einem einzigen zusätzlichen PUT-Request. Dafür schließt es eine Angriffsfläche, die in realen Incidents zu Credential-Diebstahl über SSRF geführt hat.

Nächste Schritte:

  • Bestehende Instanzen mit dem describe-instances-Befehl auf http-tokens: optional prüfen.
  • Skripte und Deployment-Pipelines auf IMDSv2-Syntax aktualisieren.
  • Neue Instanzen grundsätzlich mit HttpTokens=required starten.
  • Weiterführend: AWS-Dokumentation zu IMDS

Glossar

BegriffBedeutung
IMDSInstance Metadata Service — HTTP-Endpunkt auf 169.254.169.254, nur von der Instanz selbst erreichbar
IMDSv2Session-orientierte Version des IMDS, erfordert Token-basierte Authentifizierung per PUT/GET
SSRFServer-Side Request Forgery — Angriff, bei dem eine Anwendung dazu gebracht wird, interne Requests stellvertretend auszuführen
http-tokensInstanz-Metadaten-Option: optional erlaubt IMDSv1, required erzwingt IMDSv2
Link-lokale AdresseIP-Adresse im Bereich 169.254.0.0/16, nicht routbar außerhalb des lokalen Netzwerksegments

Related Posts

Kommentare

Beliebte Posts aus diesem Blog

EC2 ohne Internetzugang im eigenen VPC – Internet Gateway und Route Table korrekt einrichten

EC2 SSH Verbindungs-Timeout: Security Group Inbound-Regeln richtig konfigurieren