Session-lifecycle audit of a consumer marketplace app
A JWT flow that looked correct was broken in two places: refresh tokens stayed valid after rotation, logout was cosmetic only. The audit exposed a high-severity chain that would have enabled account-takeover after expired cookies.
Kontext / Context
Eine Consumer-App mit authentifizierten Flows und Zahlungs-Relevanz. Fokus des Engagements: Refresh-Token-Rotation, Logout-Semantik, Cross-Device-Sessions. JWT-basiert, mit Access- und Refresh-Token-Paar.
Finding-Klassen (abstrahiert)
- Refresh-Token-Rotation ohne Invalidation. Der alte Refresh-Token blieb nach Rotation gültig. Ein gekaperter Refresh-Token kann beliebig lang neue Access-Tokens generieren, auch nachdem der Nutzer längst einen neuen Refresh hat.
- Logout-Endpoint kosmetisch. Logout löschte nur den Client-seitigen Cookie. Access- und Refresh-Token blieben serverseitig gültig bis zu ihrer jeweiligen
exp-Zeit. - Keine Revocation-Liste. Der JWT-Verifier prüfte nur Signatur und
exp. Keine Mechanik für einen kompromittierten Token, ihn vor Ablauf zu invalidieren.
Methodik
- Kontrollierter MITM-Capture über eigene Test-Accounts und eigenes Test-Device
- Vollständiger Auth-Flow-Walk mit mehreren Rotations-Zyklen
- Parallele Replay-Tests mit altem und neuem Token
- Logout-Semantik mit mehreren Replay-Varianten
Deliverable
Finding-Bundle mit Root-Cause-Analyse, Repro-Scripts, CVSS-Berechnung, drei konkrete Remediation-Pfade (Revocation-Liste in einem Redis-ähnlichen Store, kürzere Access-Token-TTL, Token-Family-Tracking für Refresh-Rotation mit reuse-detection).
Learnings
JWT-Auth ist genau dann fragil, wenn das System Signatur und exp als Gesamt-Validierung betrachtet. Sobald Refresh-Tokens eine lange TTL haben (üblich: 30 Tage bis 1 Jahr), braucht es zwingend: Revocation, Family-Tracking, Reuse-Detection. Das Pattern ist nicht kreativ, wird aber in der Praxis häufig übersehen, weil “the library handles it” meist nur Signatur und exp macht.