security: OAuth Auth-Bypass schliessen (client_secret + PKCE jetzt Pflicht)
KRITISCH: /token akzeptierte authorization_code OHNE client_secret
('rely on PKCE alone'-Zweig) und ueberging PKCE wenn code_challenge leer war.
Da client_id nur der Benutzername ist (z.B. 'stefan', steht in der Subdomain),
konnte jeder mit ratbarem Namen einen 30-Tage-Vollzugriffs-Token holen
(verifiziert ausnutzbar). Token galt zudem fuer alle 5 MCP-Dienste.
Fix:
- /authorize: code_challenge (PKCE) PFLICHT
- /token: client_secret PFLICHT (_resolve_client) UND PKCE-Verifikation PFLICHT,
Bypass-Zweige entfernt.
claude.ai sendet beides (client_secret_post + S256-PKCE, per Audit verifiziert)
-> kein erneutes Verbinden noetig, bestehende Tokens bleiben gueltig.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -129,6 +129,9 @@ async def oauth_authorize(request: Request):
|
||||
if client_id not in tokens:
|
||||
return HTMLResponse(f"<h1>Fehler</h1><p>Unbekannte Client ID: {client_id}</p>", status_code=400)
|
||||
|
||||
if not code_challenge:
|
||||
return HTMLResponse("<h1>Fehler</h1><p>PKCE erforderlich (code_challenge fehlt).</p>", status_code=400)
|
||||
|
||||
code = secrets.token_urlsafe(32)
|
||||
_auth_codes[code] = {
|
||||
"client_id": client_id,
|
||||
@@ -171,19 +174,16 @@ async def oauth_token(request: Request):
|
||||
if code_data["client_id"] != client_id:
|
||||
return JSONResponse({"error": "invalid_grant", "error_description": "Client ID mismatch"}, status_code=400)
|
||||
|
||||
if code_data["code_challenge"]:
|
||||
if not _verify_pkce(code_verifier, code_data["code_challenge"], code_data["code_challenge_method"]):
|
||||
return JSONResponse({"error": "invalid_grant", "error_description": "PKCE verification failed"}, status_code=400)
|
||||
# PKCE PFLICHT (kein Bypass mit leerem code_challenge)
|
||||
if not code_data.get("code_challenge"):
|
||||
return JSONResponse({"error": "invalid_grant", "error_description": "PKCE required"}, status_code=400)
|
||||
if not _verify_pkce(code_verifier, code_data["code_challenge"], code_data["code_challenge_method"]):
|
||||
return JSONResponse({"error": "invalid_grant", "error_description": "PKCE verification failed"}, status_code=400)
|
||||
|
||||
# Verify client_secret if provided, otherwise rely on PKCE alone
|
||||
if client_secret:
|
||||
user = _resolve_client(client_id, client_secret)
|
||||
if not user:
|
||||
return JSONResponse({"error": "invalid_client", "error_description": "Invalid client credentials"}, status_code=401)
|
||||
else:
|
||||
user = client_id
|
||||
if user not in _load_tokens():
|
||||
return JSONResponse({"error": "invalid_client"}, status_code=401)
|
||||
# client_secret PFLICHT (confidential client) -- schliesst den Auth-Bypass
|
||||
user = _resolve_client(client_id, client_secret)
|
||||
if not user:
|
||||
return JSONResponse({"error": "invalid_client", "error_description": "Invalid client credentials"}, status_code=401)
|
||||
|
||||
elif grant_type == "client_credentials":
|
||||
user = _resolve_client(client_id, client_secret)
|
||||
|
||||
Reference in New Issue
Block a user