Zum Inhalt springen

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

↑↓NavigierenEnterÖffnenESCSchließen
Web-Sicherheit Glossar

Open Redirect - Offene Weiterleitung

Open Redirect ist eine Webanwendungsschwachstelle bei der ein Angreifer die Redirect-Funktionalität einer legitimen Website missbraucht um Nutzer auf eine externe Angreifer-Website umzuleiten. Obwohl technisch keine direkte Code-Execution, wird Open Redirect als Phishing-Katalysator (legitime URL täuscht über Ziel), OAuth/OIDC Token-Diebstahl (redirect_uri Manipulation) und SSRF-Hilfsmittel eingesetzt. In OWASP API Security ist Open Redirect relevant für A3:2023 (Broken Object Property Level Authorization).

Open Redirect klingt harmlos - und wird von vielen Entwicklern als Low-Severity-Finding abgetan. Aber kombiniert mit OAuth 2.0 wird aus einem “harmlosen” Redirect ein kritisches Token-Diebstahl-Szenario. Und als Phishing-Vehikel ist https://legitime-bank.de/redirect?url=https://evil.com/login für Opfer fast unmöglich zu erkennen. Open Redirect taucht regelmäßig in Bug-Bounty-Reports auf - oft mit beachtlichen Prämien wenn OAuth-Kombination gefunden.

Open Redirect Grundprinzip

Typische Implementierung mit Schwachstelle:

Legitime Verwendung:
  # Nach Login: Nutzer zurück zur angeforderten Seite leiten
  GET /login?redirect=/dashboard
  → Nach Login: 302 Location: /dashboard ✓

Schwachstelle:
  GET /login?redirect=https://evil.com/fake-login
  → Nach Login: 302 Location: https://evil.com/fake-login ✗

  Oder:
  GET /logout?next=https://phishing.com/
  GET /redirect?url=https://evil.com/

Häufige Parameter-Namen für Open Redirect:
  url=, redirect=, next=, return=, returnTo=, redirect_uri=
  dest=, destination=, target=, rurl=, forward=, goto=
  continue=, back=, from=, origin=, to=, out=

Angriffs-Szenarien

Phishing-Angriff via Open Redirect:

  # Normaler Phishing-Link:
  https://evil.com/fake-banking-login/
  → Sicherheitsbewusster User: verdächtige Domain erkannt!

  # Mit Open Redirect auf legitimer Bank-Domain:
  https://real-bank.de/logout?redirect=https://evil.com/fake-banking-login/
  → URL beginnt mit echter Bank-Domain!
  → E-Mail-Filter: echte Domain im Link - weniger verdächtig
  → User sieht: "https://real-bank.de/..." → klickt!
  → Landet auf Phishing-Seite

---

OAuth/OIDC Token-Diebstahl:

  # Normaler OAuth-Flow:
  GET /authorize?client_id=myapp&redirect_uri=https://myapp.com/callback&...
  → Nach Auth: Code gesendet an https://myapp.com/callback (valide!)

  # Mit Open Redirect auf Authorization Server:
  # Wenn Authorization Server selbst Open Redirect hat:
  https://auth.example.com/authorize?client_id=myapp&
    redirect_uri=https://auth.example.com/redirect?url=https://evil.com/

  # Flow:
  1. Auth Server redirectet nach Login zu:
     https://auth.example.com/redirect?url=https://evil.com/&code=SECRET_CODE
  2. Open Redirect leitet weiter zu:
     https://evil.com/?code=SECRET_CODE
  3. Angreifer stiehlt den Authorization Code!
  4. Tauscht Code gegen Access Token

  → Kritische Kombination! Code-Severity: High/Critical

---

SSRF via Open Redirect:

  # SSRF-Filter prüft nur externe URL:
  # Wenn App SSRF-Schutz hat aber Open Redirect nicht berücksichtigt:

  fetch?url=https://legitimate-site.com/redirect?url=http://169.254.169.254/

  # Schutzmaßnahme (scheinbar):
  → URL wird geprüft: legitimate-site.com → erlaubt ✓

  # Exploit:
  → Server folgt Redirect zu http://169.254.169.254/
  → SSRF-Schutz umgangen!

---

Credential-Theft via Referrer-Header:

  # Wenn externe Seite: Authorization Code in URL + Referrer:
  # User kommt von:
  https://auth.site.com/callback?code=SECRET&state=xyz
  → Klickt auf Link auf Callback-Seite zu externem Provider
  → Referrer-Header enthält: https://auth.site.com/callback?code=SECRET!
  → Code an External Site geleaked!

Erkennung und Prävention

Programmatischer Schutz:

Allowlist-basierter Redirect (Best Practice):
  # Python (Flask):
  from urllib.parse import urlparse

  ALLOWED_DOMAINS = {'myapp.com', 'www.myapp.com', 'api.myapp.com'}

  def safe_redirect(url: str, default: str = '/') -> str:
      """Nur auf erlaubte Domains redirecten"""
      if not url:
          return default

      parsed = urlparse(url)

      # Relative URLs erlauben (kein Schema → intern):
      if not parsed.netloc and not parsed.scheme:
          # Nur /pfad/... - keine Domain-Angabe
          return url

      # Externe URLs: Domain-Prüfung
      if parsed.netloc.rstrip('/') not in ALLOWED_DOMAINS:
          return default  # Unbekannte Domain → Default

      # Schema-Prüfung (kein javascript:, data:, etc.)
      if parsed.scheme not in ('http', 'https'):
          return default

      return url

  # Usage:
  redirect_url = request.args.get('redirect', '/')
  return redirect(safe_redirect(redirect_url))

JavaScript (Next.js):
  // Middleware oder API Route:
  function safeRedirect(url: string, fallback = '/'): string {
      try {
          // Relative URLs direkt erlauben
          if (url.startsWith('/') && !url.startsWith('//')) {
              return url;
          }
          const parsed = new URL(url);
          const allowed = ['myapp.com'];
          if (allowed.includes(parsed.hostname)) {
              return url;
          }
          return fallback;
      } catch {
          return fallback;  // Ungültige URL
      }
  }

PHP:
  function safe_redirect(string $url, string $default = '/'): string {
      // Nur relative Pfade und eigene Domain
      $parsed = parse_url($url);
      if (!isset($parsed['host'])) {
          return $url; // Relative URL
      }
      $allowed = ['example.com', 'www.example.com'];
      return in_array($parsed['host'], $allowed) ? $url : $default;
  }

Häufige Fehler:
  # Unzureichende Prüfung (bypassbar!):

  # Nur Präfix prüfen - bypassbar mit ?:
  if starts_with(url, 'https://mysite.com'):
      redirect(url)
  # Bypass: https://mysite.com.evil.com/ oder https://mysite.com@evil.com/

  # Nur Host-Match - bypassbar mit @:
  parsed = urlparse(url)
  if parsed.netloc == 'mysite.com':
      redirect(url)
  # Bypass: https://mysite.com@evil.com/ → netloc ist 'mysite.com@evil.com'
  # → parsed.netloc != 'mysite.com' ABER Browser navigiert zu evil.com!

  # JavaScript-URI vergessen:
  if not url.startswith('http://evil'):
      redirect(url)
  # Bypass: javascript:alert(1) oder data:text/html,...

---

OAuth-spezifische Maßnahmen:

  # Authorization Server: redirect_uri EXAKT matchen!
  # Keine Wildcards, kein Prefix-Match - nur exacte Übereinstimmung!

  # Schlecht:
  allowed_uris = ['https://myapp.com/callback*']  # Wildcard - gefährlich!
  if url.startswith(allowed_uris):  # Prefix - gefährlich!

  # Gut:
  ALLOWED_REDIRECT_URIS = {
      'https://myapp.com/callback',
      'https://myapp.com/oauth/callback',
  }
  if redirect_uri not in ALLOWED_REDIRECT_URIS:
      return error('invalid_redirect_uri')

  # State Parameter für CSRF-Schutz (gegen Session-Fixation):
  state = secrets.token_urlsafe(32)
  session['oauth_state'] = state
  # → Bei Callback: state aus Session prüfen!

Testing im Pentest

Open Redirect Testing:

Parameter-Discovery:
  # Burp Intruder: alle Parameter mit Redirect-Semantik testen
  # Wordlist: url, redirect, next, return, goto, dest, ...

  # Manuell: Link-Elemente im Response durchsuchen
  grep -E "(href|action|src|redirect|url)=[\"']/" response.html

Test-Payloads:
  # Externe Domain:
  ?redirect=https://evil.com/
  ?redirect=//evil.com/          ← Protocol-relative URL
  ?redirect=https:evil.com       ← Fehlende Slashes (einige Browser!)

  # Encoding-Varianten:
  ?redirect=%68%74%74%70%73%3a%2f%2fevil.com  ← URL-encoded
  ?redirect=https://evil%2Ecom/              ← Dot-encoded

  # Bypass-Versuche:
  ?redirect=https://legitimate-site.evil.com/
  ?redirect=https://legitimate-site.com.evil.com/
  ?redirect=https://legitimate-site.com@evil.com/

  # JavaScript-URI:
  ?redirect=javascript:alert(1)
  ?redirect=data:text/html,<script>alert(1)</script>

  # SSRF via Redirect:
  ?redirect=http://169.254.169.254/latest/meta-data/

  # Für OAuth-Kombination:
  # redirect_uri mit Open-Redirect-Kette testen

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