rehash/cleanup: Vorzustand sichern + Bestaetigung verlangen (Pentest R170-01)
POST /audit-logs/rehash rechnet die Kette mit dem HMAC-Schluessel neu und macht
sie damit wieder stimmig - auch wenn sie vorher berechtigte Beanstandungen
enthielt. Der Anker schuetzt gegen einen DB-Schreiber ohne Schluessel, nicht
gegen einen Admin mit audit:admin. Der bisherige Marker hielt nur fest, DASS
rehasht wurde, nicht WAS dabei verschwand.
Fix 1: Vor dem Rehash wird verifyIntegrity() erhoben und samt Kettenkopf im
Marker gesichert - Anzahl geprueft, Listen der manipulierten Zeilen, der
Ketten-Luecken, der Luecken ohne dokumentierte Loeschung, der nicht pruefbaren.
Dazu ausloesender Benutzer und IP statt pauschal "system". Der Marker entsteht
nach dem Rehash, ist Teil der neuen Kette und signiert.
Fix 2: rehash verlangt {"confirm":"REHASH"}, cleanup verlangt
{"confirm":"CLEANUP"}. Beide wurden bei blinder Methoden-Erkundung per POST
unbeabsichtigt ausgeloest; ein tastender Aufruf laeuft jetzt in 400.
Verifiziert: blinder POST -> 400 ohne Wirkung; mit Bestaetigung laeuft der
Rehash und der Marker enthaelt Ausloeser, Vorbefund (1 manipuliert, 7 Luecken
mit exakten IDs) und Kettenkopf.
Hinweis: Der Test hat auf der DEV-Datenbank real rehasht, die dortigen
historischen Beanstandungen sind damit geglaettet. Staging/Prod unberuehrt.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -869,7 +869,27 @@ export async function verifyIntegrity(fromId?: number, toId?: number): Promise<{
|
||||
/**
|
||||
* Hash-Kette komplett neu berechnen (Reparatur)
|
||||
*/
|
||||
export async function rehashAll(): Promise<{ rehashedCount: number }> {
|
||||
export async function rehashAll(
|
||||
ausgeloestVon?: { userEmail?: string; ipAddress?: string },
|
||||
): Promise<{ rehashedCount: number }> {
|
||||
// ZUSTAND VOR DEM REHASH SICHERN (Pentest R170-01)
|
||||
//
|
||||
// Ein Rehash macht die Kette rechnerisch wieder stimmig – auch dann, wenn sie
|
||||
// vorher berechtigte Beanstandungen enthielt. Der bisherige Marker hielt nur
|
||||
// fest, DASS rehasht wurde, nicht WAS dabei verschwand. Wer `audit:admin`
|
||||
// besitzt, konnte damit Spuren glattziehen, ohne dass hinterher erkennbar
|
||||
// war, welche.
|
||||
//
|
||||
// Deshalb wird der Befund samt Kettenkopf VOR dem Rehash erhoben und im
|
||||
// Marker mitgeschrieben. Der Marker entsteht nach dem Rehash, ist selbst
|
||||
// Teil der neuen Kette und signiert; entfernen liesse er sich nur unter
|
||||
// Hinterlassung einer Luecke.
|
||||
const vorher = await verifyIntegrity();
|
||||
const kopf = await prisma.auditLog.findFirst({
|
||||
orderBy: { id: 'desc' },
|
||||
select: { id: true, hash: true, hashVersion: true, createdAt: true },
|
||||
});
|
||||
|
||||
const logs = await prisma.auditLog.findMany({
|
||||
orderBy: { id: 'asc' },
|
||||
select: {
|
||||
@@ -925,15 +945,33 @@ export async function rehashAll(): Promise<{ rehashedCount: number }> {
|
||||
// haengt sich an die neu berechnete Kette; entfernen liesse er sich nur unter
|
||||
// Hinterlassung einer Luecke.
|
||||
await createAuditLog({
|
||||
userEmail: 'system',
|
||||
userEmail: ausgeloestVon?.userEmail || 'system',
|
||||
userRole: 'System',
|
||||
action: 'UPDATE',
|
||||
resourceType: 'AuditLog',
|
||||
resourceLabel: `Hash-Kette neu berechnet (${count} Einträge) – Beweiskraft der Vergangenheit zurückgesetzt`,
|
||||
resourceLabel:
|
||||
`Hash-Kette neu berechnet (${count} Einträge) – Beweiskraft der Vergangenheit zurückgesetzt` +
|
||||
(vorher.valid
|
||||
? ' – Kette war vorher unbeanstandet'
|
||||
: ` – vorher beanstandet: ${vorher.tamperedEntries.length} manipuliert, ` +
|
||||
`${vorher.chainGaps.length} Lücken`),
|
||||
endpoint: '/api/audit-logs/rehash',
|
||||
httpMethod: 'POST',
|
||||
ipAddress: 'system',
|
||||
ipAddress: ausgeloestVon?.ipAddress || 'system',
|
||||
sensitivity: 'CRITICAL',
|
||||
// Befund VOR dem Rehash – ohne das waere nach dem Rehash nicht mehr
|
||||
// nachvollziehbar, was uebertuencht wurde.
|
||||
changesBefore: {
|
||||
geprueft: vorher.checkedCount,
|
||||
manipuliert: vorher.tamperedEntries,
|
||||
ketten_luecken: vorher.chainGaps,
|
||||
luecken_ohne_dokumentierte_loeschung: vorher.unexplainedGaps,
|
||||
nicht_pruefbar: vorher.unverifiableEntries,
|
||||
kettenkopf: kopf
|
||||
? { id: kopf.id, hash: kopf.hash, hashVersion: kopf.hashVersion, createdAt: kopf.createdAt }
|
||||
: null,
|
||||
},
|
||||
changesAfter: { neuBerechnet: count },
|
||||
success: true,
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user