Security-Hardening Runde 10: Pentest Runde 6 (8 Findings + struktureller Audit-Sweep)
KRITISCH: - emails/:id/thread bekommt canAccessCachedEmail - customers/:customerId/representatives/search bekommt canAccessCustomer (Buchstaben-Brute-Force konnte sonst die Kunden-DB enumerieren) HOCH: - birthdays/upcoming: Portal-User → 403 (Name/E-Mail/Telefon/Geb-Datum aller Kunden leakte) - contracts/:id/history (GET/POST/PUT/DELETE) bekommt canAccessContract - mailbox-accounts / unread-count / contracts/:id/emails/folder-counts bekommen canAccessCustomer bzw. canAccessContract - Vertreter-Vollmacht-Check ist jetzt live: neuer Helper getPortalAllowedCustomerIds() in accessControl.ts ruft hasAuthorization() für jedes vertretene Customer ab. Eingesetzt in getTasks/createSupportTicket/createCustomerReply/getAllTasks/ getTaskStats und updateCustomerConsent. Widerrufene Vollmachten haben jetzt SOFORT keinen Zugriff mehr (vorher: bis JWT abläuft). MITTEL: - confirmPasswordReset speichert portalPasswordEncrypted nicht mehr beim Self-Service-Reset (war nur für Admin-OTPs gedacht); + portalPasswordMustChange=false explizit - getCustomers pagination total reflektiert jetzt nur erlaubte IDs (über DB-Filter in customerService.getAllCustomers) Audit-Sweep (defense in depth, falls Rolle versehentlich Update- Permissions bekommt): - 16 cachedEmail-Operationen (markAsRead, toggleStar, assign/unassign, save-as-pdf/invoice/contract-document, save-to, attachment-targets, trash-ops) - 4 contract-Operationen (createFollowUp, createRenewal, snoozeContract, removeContractMeter) - 12 sub-CRUD-Operationen (address/bankcard/document/meter update+delete, meter-reading add/update/delete/transfer) - 2 representative-Operationen (add/remove) Live-verifiziert: Portal-Customer-3 auf alle fremden IDs → 403, Admin sieht alles, eigene Ressourcen weiterhin 200, Customer 1 mit widerrufener Vollmacht für Customer 3 → 0 fremde Verträge in der Response. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -97,6 +97,67 @@ isolierte Instanz (keine Multi-Tenancy im Code), Provisioning + Abrechnung
|
||||
|
||||
## ✅ Erledigt
|
||||
|
||||
- [x] **🚨 Pentest Runde 6 – Sammelfix + Strukturelles Audit (8 Findings + Audit-Sweep)**
|
||||
- **KRITISCH-01 `GET /emails/:id/thread`**: kein Owner-Check →
|
||||
Portal-Kunde konnte alle Mail-Threads durchsuchen. Fix:
|
||||
`canAccessCachedEmail` im Controller.
|
||||
- **KRITISCH-02 `GET /customers/:customerId/representatives/search`**:
|
||||
kein `canAccessCustomer` auf den Pfad → DSGVO-GAU, Portal-Kunde
|
||||
konnte mit Buchstaben-Brute-Force die komplette Kunden-DB
|
||||
auslesen. Fix eingefügt.
|
||||
- **HOCH-01 `GET /birthdays/upcoming`**: kein Portal-Filter → Name,
|
||||
E-Mail, Telefon, Geburtsdatum aller Kunden lesbar. Fix:
|
||||
`isCustomerPortal` → 403.
|
||||
- **HOCH-02 `*/contracts/:contractId/history`**: kein Owner-Check
|
||||
auf GET/POST/PUT/DELETE. Fix: `canAccessContract` in allen vier
|
||||
History-Handlern.
|
||||
- **HOCH-03 Mailbox-Endpoints**: `mailbox-accounts`, `unread-count`,
|
||||
`contracts/:id/emails/folder-counts` ohne Check. Fix:
|
||||
`canAccessCustomer` bzw. `canAccessContract` in allen drei.
|
||||
- **HOCH-04 Live-Vollmacht-Check in Tasks**: `getTasks`,
|
||||
`createSupportTicket`, `createCustomerReply`, `getAllTasks`,
|
||||
`getTaskStats` prüften nur `representedCustomerIds.includes(...)`
|
||||
aus dem JWT – widerrufene Vollmachten hatten weiter Zugriff
|
||||
(JWT lebt bis zu 15min nach Widerruf). Neuer Helper
|
||||
`getPortalAllowedCustomerIds()` in `accessControl.ts` ruft
|
||||
`hasAuthorization()` live ab. Auch `updateCustomerConsent`
|
||||
(GDPR) auf diesen Pfad umgestellt.
|
||||
- **MITTEL-01 `confirmPasswordReset` Klartext-Speicherung**:
|
||||
Self-Service-Reset speicherte `portalPasswordEncrypted = encrypt(pw)`.
|
||||
Klartext-Speicherung ist nur für Admin-OTPs sinnvoll. Fix:
|
||||
Field auf null, zusätzlich `portalPasswordMustChange = false`.
|
||||
- **MITTEL-02 Pagination-Total leakt globale Kunden-Anzahl**:
|
||||
`GET /customers` gab `total: 4271` auch wenn Portal-User nur
|
||||
1 Kunde sah. Fix: `customer.service.ts` erweitert um
|
||||
`allowedIds`-Filter, der direkt in der DB-Query landet → die
|
||||
pagination zählt nur über erlaubte IDs.
|
||||
- **Strukturelles Audit-Sweep** (Sub-CRUD + Email-Operationen):
|
||||
Folgende Handler bekamen jetzt erstmals einen `canAccess*`-
|
||||
Check, defense in depth gegen falsch vergebene Rollen:
|
||||
`markAsRead`, `toggleStar`, `assignToContract`,
|
||||
`unassignFromContract`, `deleteEmail`, `getTrashEmails`,
|
||||
`getTrashCount`, `restoreEmail`, `permanentDeleteEmail`,
|
||||
`getAttachmentTargets`, `saveAttachmentTo`, `saveEmailAsPdf`,
|
||||
`saveEmailAsInvoice`, `saveAttachmentAsInvoice`,
|
||||
`saveAttachmentAsContractDocument`, `createFollowUp`,
|
||||
`createRenewal`, `snoozeContract`, `removeContractMeter`,
|
||||
`updateAddress`, `deleteAddress`, `updateBankCard`,
|
||||
`deleteBankCard`, `updateDocument`, `deleteDocument`,
|
||||
`updateMeter`, `deleteMeter`, `addMeterReading`,
|
||||
`updateMeterReading`, `deleteMeterReading`,
|
||||
`markReadingTransferred`, `addRepresentative`,
|
||||
`removeRepresentative`.
|
||||
- **Live-verifiziert** (Portal-User Customer 3 auf fremde IDs):
|
||||
`customers/1/representatives/search` → 403,
|
||||
`birthdays/upcoming` → 403 (Admin → 200),
|
||||
`emails/21/thread` → 403,
|
||||
`customers/2/mailbox-accounts` → 403,
|
||||
`emails/unread-count?customerId=2` → 403,
|
||||
`contracts/8/{history,folder-counts,follow-up,renewal,snooze}` → 403,
|
||||
eigene `customers/3` → 200,
|
||||
pagination.total für Portal = 1 (statt 3),
|
||||
Customer 1 mit widerrufener Vollmacht → 0 fremde Verträge.
|
||||
|
||||
- [x] **🚨 Pentest Runde 5 – KRITISCH: change-initial-portal-password ohne Pflicht-Check**
|
||||
- **Realer Angriff**: Jeder Portal-User konnte jederzeit mit
|
||||
seinem eingeloggten Token `POST /api/auth/change-initial-portal-
|
||||
|
||||
Reference in New Issue
Block a user