Audit-Integritaet: Dauer-Fehlalarm ueber 67 % des Logs behoben
verifyIntegrity meldete 3107 von 4630 Zeilen als "manipuliert" - davon 3100 Fehlalarme, exakt die Zeilen mit resourceId = NULL aus 08.02.-01.05.2026. Ursache: Der R121-Fix nahm an, resourceId sei beim Schreiben immer undefined gewesen (Key faellt bei JSON.stringify weg) und daher wuerden alle Bestands-Hashes ohne Rehash matchen. Das gilt erst ab ~01.05.2026 - aeltere Zeilen wurden mit explizitem null serialisiert, der Key war drin. Sicherheitsrelevant, weil ein staendig grundlos ausloesender Alarm ignoriert wird - echte Manipulation ginge im Laerm unter. Fix: generateHashLegacy() reproduziert das alte Schreibverhalten, verifyIntegrity akzeptiert Altbestand ueber diesen Fallback (nur geprueft, wenn die aktuelle Variante nicht passt). Bewusst KEIN Rehash - der wuerde die Manipulations-Beweiskraft der Vergangenheit zerstoeren. Gespeicherte Hashes bleiben unangetastet. Verifiziert: ungueltig 3107 -> 7 (echte Ketten-Brueche). Adversarial gegengetestet: Manipulation an userEmail/action/endpoint/createdAt/resourceId wird bei alten wie neuen Zeilen zu 100 % erkannt (10/10). tsc gruen. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -119,6 +119,48 @@ function generateHash(data: {
|
||||
return crypto.createHash('sha256').update(JSON.stringify(payload)).digest('hex');
|
||||
}
|
||||
|
||||
/**
|
||||
* Historische Hash-Variante (Bestandsdaten ~08.02.–01.05.2026).
|
||||
*
|
||||
* Der R121-Fix ging davon aus, dass `resourceId` beim Schreiben immer
|
||||
* `undefined` war (→ Key faellt bei JSON.stringify weg) und daher ALLE
|
||||
* Bestands-Hashes ohne Rehash matchen. Das stimmt erst ab ~01.05.2026:
|
||||
* aeltere Zeilen wurden mit explizitem `null` serialisiert, der Key war also
|
||||
* DRIN. Ergebnis war ein Dauer-Fehlalarm ueber ~3100 Zeilen (67 % des Logs) –
|
||||
* und ein Alarm, der staendig grundlos ausloest, verdeckt echte Manipulation.
|
||||
*
|
||||
* Diese Funktion reproduziert exakt das alte Schreibverhalten, damit
|
||||
* verifyIntegrity Altbestand als gueltig erkennt – OHNE Rehash. Ein Rehash
|
||||
* waere der naheliegende Schnellfix, wuerde die Manipulations-Beweiskraft der
|
||||
* Vergangenheit aber unwiederbringlich zerstoeren.
|
||||
*
|
||||
* Sicherheit: Beide Varianten hashen dieselben Feldwerte, nur die
|
||||
* Serialisierung des nullish `resourceId` unterscheidet sich. Ein Angreifer
|
||||
* gewinnt dadurch keinen Spielraum, Inhalte zu aendern – nur die Kodierung
|
||||
* eines leeren Feldes ist doppelt zulaessig.
|
||||
*/
|
||||
function generateHashLegacy(data: {
|
||||
userEmail: string;
|
||||
action: AuditAction;
|
||||
resourceType: string;
|
||||
resourceId?: string | null;
|
||||
endpoint: string;
|
||||
createdAt: Date;
|
||||
previousHash?: string | null;
|
||||
}): string {
|
||||
const payload: Record<string, unknown> = {
|
||||
userEmail: data.userEmail,
|
||||
action: data.action,
|
||||
resourceType: data.resourceType,
|
||||
resourceId: data.resourceId ?? null,
|
||||
endpoint: data.endpoint,
|
||||
createdAt: data.createdAt.toISOString(),
|
||||
previousHash: data.previousHash || '',
|
||||
};
|
||||
|
||||
return crypto.createHash('sha256').update(JSON.stringify(payload)).digest('hex');
|
||||
}
|
||||
|
||||
/**
|
||||
* Bestimmt die Sensitivität basierend auf dem Ressourcentyp
|
||||
*/
|
||||
@@ -416,10 +458,23 @@ export async function verifyIntegrity(fromId?: number, toId?: number): Promise<{
|
||||
previousHash: log.previousHash,
|
||||
});
|
||||
|
||||
// Prüfen ob Hash übereinstimmt
|
||||
// Prüfen ob Hash übereinstimmt – Altbestand darf die historische
|
||||
// Serialisierung nutzen (siehe generateHashLegacy). Legacy wird nur
|
||||
// geprüft, wenn die aktuelle Variante nicht passt.
|
||||
if (log.hash !== expectedHash) {
|
||||
invalidEntries.push(log.id);
|
||||
continue;
|
||||
const legacyHash = generateHashLegacy({
|
||||
userEmail: log.userEmail,
|
||||
action: log.action,
|
||||
resourceType: log.resourceType,
|
||||
resourceId: log.resourceId,
|
||||
endpoint: log.endpoint,
|
||||
createdAt: log.createdAt,
|
||||
previousHash: log.previousHash,
|
||||
});
|
||||
if (log.hash !== legacyHash) {
|
||||
invalidEntries.push(log.id);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// Prüfen ob previousHash mit dem Hash des vorherigen Eintrags übereinstimmt
|
||||
|
||||
@@ -97,6 +97,29 @@ isolierte Instanz (keine Multi-Tenancy im Code), Provisioning + Abrechnung
|
||||
|
||||
## ✅ Erledigt
|
||||
|
||||
- [x] **🛡️ Audit-Integritaet: Dauer-Fehlalarm ueber 67 % des Logs behoben** (2026-08-18)
|
||||
- Beim Nachpruefen aufgefallen: `verifyIntegrity` meldete **3107 von 4630**
|
||||
Zeilen als „manipuliert“. Davon waren **3100 Fehlalarme** – eingegrenzt auf
|
||||
exakt die Zeilen mit `resourceId = NULL` aus dem Zeitraum 08.02.–01.05.2026.
|
||||
- Ursache: Der R121-Fix nahm an, `resourceId` sei beim Schreiben immer
|
||||
`undefined` gewesen (Key faellt bei `JSON.stringify` weg) und daher wuerden
|
||||
**alle** Bestands-Hashes ohne Rehash matchen. Das gilt erst ab ~01.05.2026 –
|
||||
aeltere Zeilen wurden mit explizitem `null` serialisiert, der Key war DRIN.
|
||||
- Warum das sicherheitsrelevant ist: Ein Alarm, der staendig grundlos
|
||||
ausloest, wird ignoriert – **echte** Manipulation ginge im Lärm unter
|
||||
(gleiches Muster wie beim Refresh-Rauschen, [R165]).
|
||||
- Fix: `generateHashLegacy()` reproduziert das alte Schreibverhalten;
|
||||
`verifyIntegrity` akzeptiert Altbestand ueber diesen Fallback (nur geprueft,
|
||||
wenn die aktuelle Variante nicht passt). **Kein Rehash** – der waere der
|
||||
naheliegende Schnellfix, wuerde die Manipulations-Beweiskraft der
|
||||
Vergangenheit aber unwiederbringlich zerstoeren. Gespeicherte Hashes
|
||||
bleiben unangetastet.
|
||||
- Verifiziert: ungueltige Zeilen **3107 → 7** (die 7 sind echte Ketten-Brueche,
|
||||
siehe naechster Punkt). Adversarial gegengetestet: Manipulation an
|
||||
`userEmail`/`action`/`endpoint`/`createdAt`/`resourceId` wird bei ALTEN wie
|
||||
NEUEN Zeilen weiterhin zu 100 % erkannt (10/10), unveraenderte Zeilen
|
||||
akzeptiert. `tsc` gruen.
|
||||
|
||||
- [x] **🐛 Audit-Log: Pfad-Matching kaputt – Auth-Actions generisch (Pentest R165-01)** (2026-08-18)
|
||||
- Pentester meldete: Entrauschung (`de0d6bd`) live **nicht wirksam** – jeder
|
||||
`/refresh` weiter `CREATE / CRITICAL / „Anmeldung erstellt“`. Zusatzbefund:
|
||||
|
||||
Reference in New Issue
Block a user