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:
@@ -148,13 +148,21 @@ export async function verifyIntegrity(req: AuthRequest, res: Response) {
|
||||
// echten Angriff aussehen liess (und damit die Meldung entwertete).
|
||||
const tampered = result.tamperedEntries.length;
|
||||
const gaps = result.chainGaps.length;
|
||||
const unexplained = result.unexplainedGaps.length;
|
||||
|
||||
const luecken = gaps > 0
|
||||
? `${gaps} strukturelle Lücken` +
|
||||
(unexplained === 0
|
||||
? ' (alle durch protokollierte Löschungen erklärt)'
|
||||
: unexplained < gaps
|
||||
? `, davon ${unexplained} ohne dokumentierte Löschung`
|
||||
: ' ohne dokumentierte Löschung')
|
||||
: '';
|
||||
|
||||
const message = tampered > 0
|
||||
? `${tampered} MANIPULIERTE Einträge gefunden` +
|
||||
(gaps > 0 ? ` (zusätzlich ${gaps} strukturelle Lücken)` : '')
|
||||
? `${tampered} MANIPULIERTE Einträge gefunden` + (gaps > 0 ? ` (zusätzlich ${luecken})` : '')
|
||||
: gaps > 0
|
||||
? `Keine Manipulation. ${gaps} strukturelle Lücken in der Verkettung ` +
|
||||
'(parallel geschriebene oder gelöschte Einträge) – Inhalte unverändert.'
|
||||
? `Keine Manipulation. ${luecken} – Inhalte unverändert.`
|
||||
: 'Alle Einträge sind unverändert und lückenlos verkettet';
|
||||
|
||||
res.json({
|
||||
@@ -167,6 +175,8 @@ export async function verifyIntegrity(req: AuthRequest, res: Response) {
|
||||
tamperedEntries: result.tamperedEntries,
|
||||
// Meist harmlos: Verkettung unterbrochen, Inhalte selbst unversehrt.
|
||||
chainGaps: result.chainGaps,
|
||||
// Nur Lücken ohne protokollierte Löschung sind erklärungsbedürftig.
|
||||
unexplainedGaps: result.unexplainedGaps,
|
||||
tampered: tampered > 0,
|
||||
message,
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user