FlawOpen
● CWE-601 / CWE-352

OAuth 2.0 redirect_uri हमले: कोड चोरी और Login CSRF

Authorization server की ढीली redirect URI जाँच या पंजीकृत callback पर open redirect से authorization code उजागर हो सकता है। इसका दुरुपयोग code binding और token endpoint की जाँच पर भी निर्भर करता है; login flow का बंधन न होना अलग से login CSRF पैदा कर सकता है।

💡 सरल भाषा में

Authorization server को एक भरोसेमंद डाकघर समझें: वह ऐप के लिए पंजीकृत वापसी पते पर ही एक बार इस्तेमाल होने वाला कोड भेजता है। पता ढीले ढंग से मिलाया जाए या पंजीकृत endpoint आगंतुक को किसी भी साइट पर भेज दे, तो कोड भटक सकता है। कोड मिल जाना अपने आप पहुँच नहीं देता; हमलावर को PKCE और जहाँ आवश्यक हो client authentication सहित token exchange की शर्तें भी पूरी करनी होंगी। अलग login CSRF दोष तब होता है जब साइट यह नहीं जाँचती कि ब्राउज़र ने वही login शुरू किया था जिसे अब पूरा किया जा रहा है।

मुख्य अवधारणाएँ और शब्द

Authorization Code
ब्राउज़र redirect से लौटने वाला अल्पकालिक credential। Client इसे token endpoint पर बदलता है; यह client और redirect URI से बँधा होता है, और PKCE होने पर verifier से भी।
redirect_uri का सटीक मिलान
RFC 9700 के अनुसार अनुरोधित URI का पहले से पंजीकृत URI से अक्षरशः मिलान होना चाहिए। Native app के localhost redirect के dynamic port के लिए सीमित अपवाद है।
state और login CSRF
Browser session से बँधे अप्रत्याशित मान को callback पर जाँचने से पुष्टि होती है कि response उपयोगकर्ता द्वारा शुरू किए गए login का है। लागू PKCE भी CSRF सुरक्षा दे सकता है; दोनों का साथ उपयोग defense-in-depth है।
PKCE (Proof Key for Code Exchange)
Client एक गुप्त verifier बनाता है और authorization request में उससे निकला challenge भेजता है। Token endpoint code बदलने के लिए verifier माँगता है, जिससे public और confidential clients में code interception का जोखिम घटता है।

Redirect और Login CSRF हमले के चरण

Path A · 1

Authorization server असुरक्षित redirect URI स्वीकार करता है

दोषपूर्ण server exact comparison के बजाय wildcard, prefix या substring नियम लगाता है। RFC 9700 native app localhost port के सीमित मामले को छोड़कर exact matching माँगता है।

Path A · 2

पंजीकृत endpoint अविश्वसनीय साइट पर भेजता है

स्वीकृत URI client के नियंत्रण वाले open redirect तक पहुँच सकता है, जैसे https://app.example/redirect?next=...। यह ढीली server जाँच को ऐप के अलग CWE-601 दोष के साथ जोड़ता है।

Path A · 3

ब्राउज़र redirect के दौरान code ले जाता है

उपयोगकर्ता की अनुमति के बाद server code को स्वीकृत URI पर लौटाता है। Open redirect अनुरोध और code को आगे भेजे तो हमलावर इसे पा सकता है। Code अपने आप bearer access token नहीं है।

Path A · 4

Token redemption को भी सफल होना होगा

चुराए गए code के उपयोग के लिए token endpoint की जाँच पास करनी होगी। मूल verifier के बिना PKCE redemption रोकता है; confidential-client authentication और redirect URI का मिलान अतिरिक्त binding देते हैं।

Path B

Response binding न होने से login CSRF हो सकता है

यदि callback ऐसे authorization response को स्वीकार करता है जो ब्राउज़र के login प्रयास से जुड़ा नहीं है, तो हमलावर पीड़ित से हमलावर के खाते में login पूरा करवा सकता है। यह account confusion है, पीड़ित के खाते तक स्वतः पहुँच नहीं।

स्रोत कोड: दोषपूर्ण और सुरक्षित कार्यान्वयन

असुरक्षित पैटर्न
# 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
मजबूत सुरक्षा
# 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")

इंजीनियरिंग सुरक्षा जाँच-सूची

← पूरी सुरक्षा निर्देशिका देखें Vulnerability Playbooks देखें → RFC 9700: OAuth 2.0 सुरक्षा की सर्वोत्तम प्रथाएँ → RFC 7636: Proof Key for Code Exchange (PKCE) → RFC 6749: Authorization Code Redirect URI Manipulation → CWE-601: Open Redirect → CWE-352: Cross-Site Request Forgery →