Security-Hardening Runde 6: Customer-Liste-Leak + XFF-Bypass + Vollmacht-Validation

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>
This commit is contained in:
2026-04-26 20:14:20 +02:00
parent 35745ce3bb
commit 0c0cecdbbd
4 changed files with 95 additions and 17 deletions
+41
View File
@@ -141,6 +141,47 @@ isolierte Instanz (keine Multi-Tenancy im Code), Provisioning + Abrechnung
- Provider/Tariff-GETs: `requirePermission('providers:read')` (Portal-Kunden sehen Provider-Liste nicht mehr)
- SMTP-Header-Injection: zentrale CRLF-Validierung in `smtpService.sendEmail` (schützt alle Caller)
- bcrypt cost 10 → 12 (OWASP 2026)
- **Runde 6 Tiefer Live-Pentest (auf Wunsch des Users, „bevor andere es tun"):**
- 🚨 **`GET /api/customers` leakte als Portal-User die komplette
Kundendatenbank** (alle Namen, E-Mails, customerNumber etc.). Der
Single-Endpoint war Stage 4 mit `canAccessCustomer` gefixt, der List-
Endpoint nicht. Jetzt: Portal-User bekommt nur eigene + vertretene
Kunden (Filter im Controller).
- 🚨 **Rate-Limit-Bypass via `X-Forwarded-For`**: 12+ Login-Versuche
mit rotierenden XFF-Werten gingen alle durch ohne 429. `trust proxy = 1`
hat naiv jedem XFF-Wert vertraut. Jetzt: `trust proxy = 'loopback'`
XFF wird nur akzeptiert wenn die Connection von 127.0.0.1 / ::1 kommt
(= lokaler Reverse-Proxy). Plus: `LISTEN_ADDR=127.0.0.1` in Production-
Default, damit das Backend nicht direkt von außen ansprechbar ist.
- **Self-Grant + Existence-Disclosure in `toggleMyAuthorization`**:
- Portal-User konnte sich selbst Vollmacht erteilen (1→1) und
Datensätze für beliebige `representativeId`s anlegen (auch nicht-
existierende, scheiterte erst auf DB-Constraint mit Prisma-Stack-Leak).
- 404 vs 403 erlaubte Existence-Probing der gesamten customer-ID-Range.
- Fix: Self-Grant 400er. Existenz + aktives `CustomerRepresentative`-
Verhältnis in einem Query, beide Fehlfälle identisch 403.
- **Prisma-Error-Leak generisch in `toggleMyAuthorization`**: keine
Prisma-Stacks mehr im Response.
- Live-verifiziert: Customer-Liste 3 statt 3000 (jetzt nur erlaubte),
Self-Grant 400, Existence-Disclosure dicht (alle 403 uniform), Auth
auf `/api/customers/:id` 200/403 (kein 404-Leak).
**Geprüft + sauber (Runde 6):**
- Prototype Pollution beim Login → kein Effekt
- HTTP-Method-Override via Header → ignoriert
- Path-Traversal in Backup-Name → durch Regex blockiert
- Developer-Routes existieren nicht (404)
- Email-Endpoints (Send/Sync/Read mit fremder StressfreiEmail-ID) → 403
- Self-grant Vollmacht via `customers/X/representatives` → 403 (perm)
- `/api/customers/:id` GET: 200 für eigene, 403 sonst (kein 404-Leak)
**Offen für v1.1:**
- `/api/contracts/:id` GET liefert 404 für nicht-existente IDs (Existence-
Probing). Da contractIds aber nicht direkt mit personenbezogenen Daten
korrelieren, niedrig-Prio. Vereinheitlichung auf 403 wäre sauberer.
- Prisma-Error-Leaks in anderen Admin-Endpoints (z.B. `addInvoice` bei
Validation-Fehler) Defense-in-Depth-Kandidat.
- **Runde 5 Hack-Das-Ding-Audit (Live-Pentest + 3 parallele Audit-Agents):**
- 🚨 **`/api/uploads/*` war OHNE AUTH erreichbar** (DSGVO-GAU!) jetzt hinter
`authenticate`. Direkte <a href>-Links nutzen `?token=...` Query-Parameter,