Tiefer Live-Pentest deckte 3 weitere Schwachstellen:
🚨 CRITICAL: GET /api/customers leakte komplette Kundendatenbank
- Stage-4 hatte canAccessCustomer auf den Single-Endpoint angewendet,
der List-Endpoint hatte nur den Daten-Sanitizer (filtert Passwort-Hashes)
aber keinen Portal-Filter. Folge: Portal-Kunde sah ALLE Kunden mit Namen,
E-Mails, customerNumber etc. – DSGVO-relevant.
- Fix: getCustomers filtert für Portal-User auf eigene + vertretene IDs.
🚨 HIGH: Rate-Limit-Bypass via X-Forwarded-For
- `trust proxy = 1` hat jedem XFF-Wert vertraut. 12+ Logins mit
rotierender XFF-IP gingen ohne 429 durch.
- Fix: `trust proxy = 'loopback'` – XFF nur noch von 127.0.0.1 / ::1
akzeptiert (= lokaler Reverse-Proxy).
- Plus: LISTEN_ADDR-Default 127.0.0.1 in Production, damit das Backend
nicht von außen direkt ansprechbar ist.
🛡 MEDIUM: Self-Grant + Existence-Disclosure in toggleMyAuthorization
- Portal-User konnte:
a) sich selbst Vollmacht erteilen (customerId=representativeId=1)
b) Authorization-Records für nicht-existierende customerIds anlegen
(scheitert erst am DB-Constraint mit vollem Prisma-Stack-Leak)
c) Customer-IDs durch 404-vs-403-Differenzen enumerieren.
- Fix: Self-Grant 400. Existenz + aktive CustomerRepresentative-Beziehung
in einem Query – Non-Existent / Non-Related geben identisch 403.
Prisma-Error-Stacks generisch ersetzt.
Live-verifiziert: Customer-Liste filtert, Self-Grant 400, Existence-Probing
dicht.
Geprüft + sauber (Runde 6, kein Bug):
- Prototype Pollution Login-Body
- HTTP-Method-Override-Header
- Path-Traversal Backup-Name (Regex blockt)
- Developer-Routes existieren nicht
- Email-Endpoints mit fremder StressfreiEmail-ID → 403
- /api/customers/:id GET liefert 403 statt 404 (kein Leak)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Live-Pentest gegen Dev-Server mit Portal-Token deckte auf, dass customer.* und
gdpr.* Endpoints nur den Data-Sanitizer, aber KEINEN canAccessCustomer-Check
hatten. Ein Portal-Kunde mit customers:read konnte per ID-Manipulation komplette
Fremddatensätze auslesen.
- customer.controller.getCustomer + getAddresses + getBankCards + getDocuments
+ getMeters + getRepresentatives + getPortalSettings: canAccessCustomer
- gdpr.controller.getCustomerConsents + getAuthorizations + checkConsentStatus:
canAccessCustomer
- createAddress/createBankCard/createDocument/createMeter (customerId aus URL):
canAccessCustomer (Defense-in-Depth – wird aktuell schon per Permission
geblockt, aber im Controller ungeschützt)
- Global Error-Handler: err.status respektieren (PayloadTooLargeError → 413
"Anfrage zu groß", SyntaxError → 400 "Ungültiges JSON" statt pauschal 500)
Live-verifiziert:
✓ /api/customers/4 als Portal → 200 VORHER, 403 NACHHER
✓ 9 andere IDOR-Endpoints gleiches Muster
✓ Eigene Daten (/api/customers/1) weiter 200
✓ 12 MB Body → 413, malformed JSON → 400
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Nach der ersten Runde habe ich parallel 3 Audit-Agents auf die Codebase
angesetzt. Die fanden noch eine Menge: Zip-Slip, Mass Assignment inkl.
Privilege Escalation, 13 weitere IDOR-Stellen, 2x Path-Traversal.
Alles gefixt. Details + Angriffsvektoren in docs/SECURITY-REVIEW.md.
🔴 KRITISCH gefixt:
1. Zip-Slip im Backup-Upload: extractAllTo() entpackte bösartige ZIPs ohne
Pfad-Validierung. Ein Angreifer mit Admin-Zugang hätte mit einem ZIP
mit Entries wie ../../etc/crontab das ganze Filesystem überschreiben
können. Jetzt wird jeder ZIP-Entry einzeln validiert (path.resolve,
starts-with-Check). Absolute Pfade + Null-Bytes werden abgelehnt.
2. Mass Assignment bei Customer/User Controllers:
- updateCustomer/createCustomer: req.body ging komplett an Prisma.
Angreifer konnte portalPasswordHash, portalPasswordResetToken,
consentHash, customerNumber direkt setzen.
- updateUser/createUser: roleIds und isActive waren übernehmbar.
**Privilege Escalation**: normaler Mitarbeiter konnte sich Admin-Rechte
durch PUT /users/:id mit {"roleIds":[1]} geben, oder andere User
deaktivieren.
Fix: Neue Whitelist-Helper pickCustomerCreate/Update, pickUserCreate/Update
in utils/sanitize.ts. Nur erlaubte Felder werden durchgelassen.
3. IDOR bei 13 weiteren Endpoints (neben denen aus Runde 1):
- GET /meters/:meterId/readings
- GET /emails/:emailId/attachments/:filename
- GET /emails/:emailId/attachments (Liste)
- GET /customers/:customerId/emails
- GET /contracts/:contractId/emails
- GET /emails/:id (einzelne Email)
- GET /stressfrei-emails/:id (leakte emailPasswordEncrypted)
- weitere…
Fix: accessControl.ts ausgebaut um canAccessAddress, canAccessBankCard,
canAccessIdentityDocument, canAccessMeter, canAccessStressfreiEmail,
canAccessCachedEmail. In allen betroffenen Endpoints angewendet.
🟡 WICHTIG gefixt:
4. Path-Traversal bei Backup-Name (GET /settings/backup/:name/*): req.params.name
wurde ohne Filter in path.join. Neuer isValidBackupName() erlaubt nur
[A-Za-z0-9_-]+ ohne "..".
5. Path-Traversal bei GDPR-Proof-Download: proofDocument-Pfad aus DB wurde
ohne Validation gejoined. Jetzt path.resolve + starts-with-uploads-Check.
Neue/erweiterte Files:
- backend/src/utils/accessControl.ts - 6 neue can-Access-Helper
- backend/src/utils/sanitize.ts - 4 neue Whitelist-pick-Helper
- docs/SECURITY-REVIEW.md - Runde 2 dokumentiert
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>