Refresh-Kulanz idempotent: stiller Session-Fork geschlossen (Pentest R168-01)
Jede Kulanz-Wiedervorlage rotierte auf einen frischen Token mit eigenem, zurueckgesetztem Zaehler. Ein Angreifer mit gestohlenem Token konnte damit aus dem erkennbaren Replay-Zustand in eine eigene, sauber weiterrotierende Sitzung entkommen, die nie wieder mit der des Opfers kollidiert - dauerhaft unsichtbar, kein einziges CRITICAL. Das hebelte die Kern-Garantie von R164-02 aus: Diebstahl faellt bei der naechsten Nutzung auf. Fix: Kulanz idempotent. Die jti des Nachfolgers wird bereits beim Einloesen im selben bedingten UPDATE reserviert (replacedByJti). Eine Wiedervorlage im Fenster gibt denselben bereits ausgestellten Nachfolger zurueck, statt neu zu rotieren - ohne neuen Datensatz. Parallele Tabs laufen dadurch auf eine Linie zusammen; wer den Token spaeter vorlegt, kollidiert zwangslaeufig und loest den Familien-Widerruf aus. Fehlt der Nachfolger, wird bewusst nicht ersatzweise rotiert (das waere wieder der Fork), sondern fail-closed als Replay gewertet. Verifiziert (PoC nachgebaut): T0 legit -> TA, T0 replayt -> TC, TA.jti == TC.jti - kein Fork mehr. Ueber HTTP: zwei Tabs beide erfolgreich auf derselben Linie; gestohlener Token nach Fensterablauf -> 401, Familie widerrufen, Angreifer-Linie tot, SUSPICIOUS/CRITICAL gemeldet. Regression: 40 parallel -> 4 erfolgreich auf einer Linie, seriell 1-4 ok und 5. Replay, Token ohne jti fail-closed, Logout widerruft. tsc gruen. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -97,6 +97,34 @@ isolierte Instanz (keine Multi-Tenancy im Code), Provisioning + Abrechnung
|
||||
|
||||
## ✅ Erledigt
|
||||
|
||||
- [x] **🔒 Refresh-Kulanz idempotent: stiller Session-Fork geschlossen (Pentest R168-01, HIGH)** (2026-08-18)
|
||||
- Der Pentester hat genau die Frage beantwortet, die ich beim Uebergeben
|
||||
gestellt hatte („laesst sich das Kulanzfenster ausnutzen?“) – und zwar
|
||||
nicht per Timing, sondern per **Linien-Fork**: Jede Kulanz-Wiedervorlage
|
||||
rotierte auf einen FRISCHEN Token mit eigenem, zurueckgesetztem Zaehler.
|
||||
Ein Angreifer mit gestohlenem Token konnte damit aus dem erkennbaren
|
||||
Replay-Zustand in eine eigene, sauber weiterrotierende Sitzung entkommen,
|
||||
die nie wieder mit der des Opfers kollidiert – **dauerhaft unsichtbar,
|
||||
kein einziges CRITICAL**. Damit war die Kern-Garantie von R164-02
|
||||
(Diebstahl faellt bei der naechsten Nutzung auf) ausgehebelt.
|
||||
- Fix (sein Vorschlag): **Kulanz idempotent**. Die jti des Nachfolgers wird
|
||||
schon beim Einloesen im selben bedingten UPDATE reserviert
|
||||
(`replacedByJti`). Eine Wiedervorlage im Fenster gibt **denselben** bereits
|
||||
ausgestellten Nachfolger zurueck, statt neu zu rotieren – ohne neuen
|
||||
Datensatz. Parallele Tabs laufen dadurch auf EINE Linie zusammen; wer den
|
||||
Token spaeter (ausserhalb des Fensters) vorlegt, kollidiert zwangslaeufig
|
||||
und loest den Familien-Widerruf aus.
|
||||
Ist kein Nachfolger hinterlegt, wird bewusst NICHT ersatzweise rotiert
|
||||
(das waere wieder der Fork), sondern fail-closed als Replay gewertet.
|
||||
- Verifiziert – sein PoC nachgebaut: T0 legit → TA, T0 replayt → TC;
|
||||
**TA.jti == TC.jti**, also kein Fork mehr. Ueber HTTP: zwei parallele Tabs
|
||||
beide erfolgreich und auf derselben Linie; gestohlener Token nach Ablauf
|
||||
des Fensters → 401, Familie widerrufen, Angreifer-Linie tot,
|
||||
`SUSPICIOUS/CRITICAL` gemeldet.
|
||||
- Regression der in R168 bestaetigten Faelle: 40 parallel → 4 erfolgreich
|
||||
(1 + Kulanz 3) auf **einer** Linie, Rest abgewiesen; serielles Replay 1–4
|
||||
ok, 5. → Replay; Token ohne jti → fail-closed; Logout widerruft. `tsc` gruen.
|
||||
|
||||
- [x] **🔁 Refresh-Token: Replay-Schutz mit Familien-Widerruf (Pentest R164-02)** (2026-08-18)
|
||||
- Die Rotation war bisher wirkungslos: Der alte Refresh-Token blieb bis `exp`
|
||||
gueltig, ein gestohlener Token also **bis zu 7 Tage parallel** zum legitimen
|
||||
|
||||
Reference in New Issue
Block a user