hashVersion-Downgrade geschlossen (Pentest R167-01, HIGH)

verifyIntegrity waehlte die Pruefstaerke nach der von der Zeile selbst
deklarierten hashVersion - und die ist nicht gehasht. Angriff: hashVersion 2->1
setzen, die nur von V2 abgedeckten Felder aendern (success false->true,
errorMessage leeren, resourceLabel umschreiben) und den schwachen V1-Hash ueber
die 7 unveraenderten Felder nachziehen. Ergebnis: Fehl-Login als Erfolg
getarnt, Pruefung meldet "gueltig". An der letzten Zeile der Kette entsteht
dabei nicht einmal ein Gap - dauerhaft unsichtbar.

Fix: Version-Floor. Die erwartete Pruefstaerke leitet sich aus der Kette ab
(MIN(id) WHERE hashVersion >= 2), nicht aus der Selbstauskunft. Ab dieser
Grenze muss jede Zeile V2 sein; weicht die deklarierte Version ab, gilt die
Zeile selbst als manipuliert. Geprueft wird immer mit dem erwarteten Verfahren.
Die Grenze laesst sich durch Herabstufen einzelner Zeilen nicht verschieben.

Zusaetzlich konsultiert die Pruefung jetzt das Loeschungs-Manifest: neu
unexplainedGaps - nur Luecken ohne protokollierte Loeschung sind
erklaerungsbeduerftig. Vorher war das Manifest rein informativ, wodurch sich
eine boeswillige Loeschung als harmloser Gap tarnen konnte.

Verifiziert (PoC nachgebaut): Downgrade mit Nachfolger erkannt, Downgrade der
Tail-Zeile erkannt, Gegenrichtung (V1 faelschlich als V2) erkannt, keine
Falschmeldungen auf Bestandsdaten. tsc + vite build gruen.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-19 18:12:13 +02:00
co-authored by Claude Opus 5
parent f2a4baacdb
commit 1a349d142e
4 changed files with 119 additions and 6 deletions
+33
View File
@@ -97,6 +97,39 @@ isolierte Instanz (keine Multi-Tenancy im Code), Provisioning + Abrechnung
## ✅ Erledigt
- [x] **🔒 hashVersion-Downgrade geschlossen (Pentest R167-01, HIGH)** (2026-08-18)
- Der Pentester hat den Angriffsweg, den ich beim Uebergeben von R166-02
selbst als naechsten benannt hatte, als **exploitbar bewiesen** (PoC gegen
Live-Code + Live-Daten): `verifyIntegrity` waehlte die Pruefstaerke nach der
von der Zeile SELBST deklarierten `hashVersion` und die ist nicht gehasht.
Angriff: `hashVersion` 2→1 setzen, die nur von V2 abgedeckten Felder
aendern (`success` false→true, `errorMessage` leeren, `resourceLabel`
umschreiben) und den schwachen V1-Hash ueber die 7 unveraenderten Felder
nachziehen → Fehl-Login als Erfolg getarnt, Pruefung meldet „gueltig“.
Besonders kritisch an der **letzten Zeile** der Kette: dort entsteht nicht
einmal ein Gap → dauerhaft unsichtbar.
- Fix: **Version-Floor.** Die erwartete Pruefstaerke leitet sich aus der Kette
ab (`MIN(id) WHERE hashVersion >= 2`), nicht aus der Selbstauskunft. Ab
dieser Grenze muss jede Zeile V2 sein; weicht die deklarierte Version von
der erwarteten ab, gilt die Zeile selbst als manipuliert. Geprueft wird
immer mit dem ERWARTETEN Verfahren. Die Grenze laesst sich durch das
Herabstufen einzelner Zeilen nicht verschieben (Minimum bleibt) ein
Angreifer muesste alle V2-Zeilen ab der Grenze herabstufen und die gesamte
Kette neu rechnen (= vollstaendiger Rehash, bekannter Grenzfall).
- Zusaetzlich: Die Pruefung konsultiert jetzt das **Loeschungs-Manifest**.
Neu `unexplainedGaps` nur Luecken ohne protokollierte Loeschung sind
erklaerungsbeduerftig. Vorher war das Manifest rein informativ, wodurch sich
eine boeswillige Loeschung als „harmloser Gap“ tarnen konnte.
- Verifiziert (PoC nachgebaut): Downgrade-Angriff auf Zeile mit Nachfolger →
**erkannt**; auf die Tail-Zeile (erzeugt keinen Gap) → **erkannt**;
Gegenrichtung (V1-Altzeile faelschlich als V2 deklariert) → **erkannt**;
Ausgangslage und Zustand nach Wiederherstellung jeweils 0 manipuliert
(keine Falschmeldungen auf Bestandsdaten). `tsc` + `vite build` gruen.
- Betriebshinweis dokumentiert: Bei einem rollierenden Deploy mit kurzzeitig
parallel schreibender Alt-Instanz koennen echte V1-Zeilen nach der Grenze
entstehen und wuerden angezeigt. Beim hier ueblichen Deploy
(pull + rebuild + restart) tritt das nicht auf.
- [x] **🛡️ Audit-Haerten: Fork, Feldabdeckung, Refresh-Rauschen, Route (Pentest R166)** (2026-08-18)
- **R166-01 (HIGH) Kette forkte weiter.** Mein GET_LOCK-Ansatz gab die
Sperre im `finally` INNERHALB des Transaktions-Callbacks frei, also VOR dem