From d5b2fc75d6ea08b8a3e3072cedd8f5d0a15aaa93 Mon Sep 17 00:00:00 2001 From: patrick Date: Thu, 10 Sep 2026 01:54:16 +0200 Subject: [PATCH] fix(kiosk): TOCTOU-Race beim QR-Login (Audit-Finding K-1) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit login_qr() las den Redis-Token per GET und löschte ihn danach separat per DELETE – zwei parallele Requests mit demselben QR-Token konnten beide vor dem Löschen lesen und je eine Session erzeugen (Doppel-Login statt echtem Einmal-Token). Fix: GETDEL (atomar seit Redis 6.2, Server läuft 8.0.2). K-2 (Cross-Tenant-Redeem) aus demselben Audit betrifft keinen realen Code-Pfad – company_id-Vergleich in login_qr() war bereits korrekt, und die Kiosk-Geräte-Enrollment "QR-Redeem"-Annahme existiert nicht als HTTP-Flow (Enrollment läuft ausschließlich über authentifizierte CLI). Verifiziert vor Weiterplanung der Android-Tablet-Kiosk-Architektur. Co-Authored-By: Claude Sonnet 5 Claude-Session: https://claude.ai/code/session_01Dzdm3EWeNdYFUuXFRJryA1 --- backend/app/services/kiosk_auth_service.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/backend/app/services/kiosk_auth_service.py b/backend/app/services/kiosk_auth_service.py index ec00e49..452c376 100644 --- a/backend/app/services/kiosk_auth_service.py +++ b/backend/app/services/kiosk_auth_service.py @@ -256,15 +256,15 @@ class KioskAuthService: redis = get_async_redis() key = QR_TOKEN_PREFIX + qr_token try: - data = await redis.get(key) + # GETDEL statt GET+DELETE: atomar, sonst können zwei parallele + # Requests denselben Token vor dem Löschen lesen (TOCTOU, Audit- + # Finding K-1) und beide eine Session erzeugen. + data = await redis.getdel(key) except Exception as exc: raise HTTPException(status_code=503, detail="QR-Login nicht verfügbar (Redis).") from exc if data is None: raise HTTPException(status_code=401, detail="QR-Code abgelaufen oder ungültig.") - # Einmalig: Token sofort löschen - await redis.delete(key) - payload = json.loads(data) if str(payload.get("company_id")) != str(company_id): raise HTTPException(status_code=401, detail="QR-Code für falsches Unternehmen.")