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:
2026-08-19 20:17:51 +02:00
co-authored by Claude Opus 5
parent 655d20db23
commit 791711ca58
2 changed files with 95 additions and 13 deletions
+28
View File
@@ -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 14
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