FlawOpen
● Weiterleitung und CSRF / CWE-601, CWE-352

OAuth 2.0: redirect_uri-Angriffe, Code-Diebstahl und Login-CSRF

Eine zu großzügige Redirect-Prüfung des Authorization Servers oder eine offene Weiterleitung auf einem registrierten Callback kann einen Authorization Code offenlegen. Eine Ausnutzung hängt weiterhin von Code-Bindung und Token-Endpunkt-Kontrollen ab; fehlende Bindung des Login-Flows kann außerdem Login-CSRF ermöglichen.

💡 Einfach erklärt

Stellen Sie sich den Authorization Server als vertrauenswürdige Poststelle vor: Er schickt einen einmaligen Code nur an die für eine App registrierte Rücksendeadresse. Wird diese Adresse zu locker abgeglichen oder leitet der registrierte Endpunkt Besucher an beliebige Websites weiter, kann der Code umgeleitet werden. Ein abgefangener Code gewährt nicht automatisch Zugriff: Der Angreifer muss auch die Anforderungen beim Token-Tausch erfüllen, einschließlich PKCE und Client-Authentifizierung, sofern erforderlich. Eine separate Login-CSRF-Schwachstelle liegt vor, wenn die Website nicht feststellen kann, ob der Browser den gerade abgeschlossenen Login selbst begonnen hat.

Kernkonzepte und wichtige Begriffe

Authorization Code
Kurzlebige Berechtigung, die per Browser-Redirect zurückgegeben wird. Der Client tauscht sie am Token-Endpunkt ein; die Einlösung ist an Client und Redirect-URI sowie bei PKCE an den Verifier gebunden.
Exakter Abgleich von redirect_uri
RFC 9700 verlangt, dass der Authorization Server die angeforderte Redirect-URI exakt als Zeichenfolge mit einer vorregistrierten URI vergleicht. Eine enge Ausnahme gilt für dynamische localhost-Ports nativer Apps.
state und Login-CSRF
Ein nicht vorhersagbarer, an die Browsersitzung gebundener und beim Callback geprüfter Wert kann die Antwort einem vom Nutzer gestarteten Login zuordnen. Erzwingtes PKCE kann ebenfalls vor CSRF schützen; beides zusammen bietet Defense in Depth.
PKCE (Proof Key for Code Exchange)
Der Client erzeugt einen geheimen Code-Verifier und sendet dessen abgeleiteten Challenge in der Autorisierungsanfrage. Der Token-Endpunkt verlangt den Verifier zur Code-Einlösung und reduziert so das Abfangrisiko für öffentliche und vertrauliche Clients.

Schrittweiser Ablauf von Redirect- und Login-CSRF-Angriffen

Pfad A · 1

Authorization Server akzeptiert eine unsichere Redirect-URI

Ein fehlerhafter Authorization Server verwendet Wildcard-, Präfix- oder Teilstring-Prüfungen statt redirect_uri exakt mit einem registrierten Wert zu vergleichen. RFC 9700 verlangt exakten Abgleich, außer bei der definierten localhost-Port-Ausnahme für native Apps.

Pfad A · 2

Ein registrierter Endpunkt leitet zu einer nicht vertrauenswürdigen Site weiter

Die akzeptierte URI kann einen vom Client kontrollierten offenen Redirect erreichen, etwa https://app.example/redirect?next=.... Hier treffen eine lockere Prüfung des Authorization Servers und eine separate CWE-601-Schwachstelle in der Client-App zusammen.

Pfad A · 3

Der Browser trägt den Code durch die Weiterleitung

Nach der Nutzerfreigabe sendet der Server den Code an die akzeptierte URI zurück. Leitet der offene Redirect die Anfrage samt Code weiter, kann der Angreifer ihn abfangen. Der Code allein ist kein Bearer-Access-Token.

Pfad A · 4

Der Token-Tausch muss weiterhin gelingen

Der Angreifer kann einen abgefangenen Code nur verwenden, wenn er die Prüfungen des Token-Endpunkts erfüllt. PKCE verhindert die Einlösung ohne den ursprünglichen Verifier; Client-Authentifizierung vertraulicher Clients und URI-Abgleich binden zusätzlich.

Pfad B

Fehlende Antwortbindung ermöglicht Login-CSRF

Akzeptiert der Callback eine Autorisierungsantwort, die nicht an den Login-Versuch des Browsers gebunden ist, kann ein Angreifer das Opfer dazu bringen, einen Flow für das Konto des Angreifers abzuschließen. Das führt zu Kontoverwechslung, nicht automatisch zum Zugriff auf das Opferkonto.

Quellcode: Fehler und sichere Implementierung

VERWUNDBARES MUSTER
# VULNERABLE: user input is embedded in redirect_uri; state and PKCE are absent.
from urllib.parse import urlencode
from flask import Flask, request, redirect

app = Flask(__name__)
AUTHORIZATION_ENDPOINT = "https://oauth-provider.example/authorize"

@app.route("/auth/login")
def oauth_login():
    next_url = request.args.get("next", "")
    # The callback is an open redirect, and this URI varies with user input.
    redirect_uri = "https://app.example/redirect?next=" + next_url
    params = {
        "client_id": "example-client",
        "response_type": "code",
        "redirect_uri": redirect_uri,
    }
    # Exploitation also requires the authorization server to accept this URI
    # under an unsafe wildcard or prefix rule.
    return redirect(AUTHORIZATION_ENDPOINT + "?" + urlencode(params))

@app.route("/auth/callback")
def oauth_callback():
    # No state check ties this response to the browser's login attempt.
    code = request.args.get("code")
    return exchange_code_for_tokens(code)  # no PKCE verifier provided
ABGESICHERTE ABWEHR
# SECURE: fixed registered redirect URI, session-bound state, and PKCE.
import base64
import hashlib
import secrets
from urllib.parse import urlencode
from flask import Flask, request, redirect, session, abort

app = Flask(__name__)
AUTHORIZATION_ENDPOINT = "https://oauth-provider.example/authorize"
REDIRECT_URI = "https://app.example/auth/callback"  # exact registered value
CLIENT_ID = "example-client"

@app.route("/auth/login")
def oauth_login():
    state = secrets.token_urlsafe(32)
    verifier = secrets.token_urlsafe(64)
    challenge = base64.urlsafe_b64encode(
        hashlib.sha256(verifier.encode("ascii")).digest()
    ).rstrip(b"=").decode("ascii")
    session["oauth_state"] = state
    session["pkce_verifier"] = verifier
    params = {
        "client_id": CLIENT_ID,
        "response_type": "code",
        "redirect_uri": REDIRECT_URI,
        "state": state,
        "code_challenge": challenge,
        "code_challenge_method": "S256",
    }
    return redirect(AUTHORIZATION_ENDPOINT + "?" + urlencode(params))

@app.route("/auth/callback")
def oauth_callback():
    expected = session.pop("oauth_state", None)
    if not expected or not secrets.compare_digest(
        request.args.get("state", ""), expected
    ):
        abort(403, "Invalid OAuth state")

    code = request.args.get("code")
    verifier = session.pop("pkce_verifier", None)
    if not code or not verifier:
        abort(400, "Missing authorization response")

    # The token request must use this exact redirect_uri and verifier.
    tokens = exchange_code_for_tokens(
        code=code, redirect_uri=REDIRECT_URI, code_verifier=verifier
    )
    session["user_id"] = tokens["user_id"]
    return redirect("/dashboard")

Checkliste zur technischen Absicherung

← Vollständiges Sicherheitsverzeichnis durchsuchen Schwachstellen-Leitfäden ansehen → RFC 9700: OAuth 2.0 Security Best Current Practice → RFC 7636: Proof Key for Code Exchange (PKCE) → RFC 6749: Manipulation der Authorization-Code-Redirect-URI → CWE-601: Offene Weiterleitung → CWE-352: Cross-Site Request Forgery →