Pentest R120 CRITICAL: IDOR auf Vertragsbaum-Endpunkt schließen

GET /api/contracts?tree=true&customerId=<fremd> gab Portal-Usern
den vollständigen Vertragsbaum beliebiger Fremdkunden zurück
(Name, Kundennummer, Vertragsnummern, Tarife – HTTP 200). Der
tree=true-Zweig returnte früh, bevor die Portal-User-customerIds-
Filterung griff, die für die flache Liste im selben Handler läuft.
Vorbestehender Bug; der includeDeactivated-Toggle machte ihn nur
sichtbarer (auch archivierte Fremdverträge kamen mit). Live vom
Pentester bestätigt, Gegentest ohne den Param = identisches Leck.

Fix: canAccessCustomer(req, res, customerId) vor dem frühen
Return. Prüft eigene ID + vertretene MIT Live-Vollmacht, sendet
selbst die 403. Staff passiert unverändert. Gleiches Muster wie
Pentest 56.3 bei update/delete.

Flacher Listenpfad war nie betroffen: getAllContracts bevorzugt
das serverseitig gesetzte customerIds-Scope gegenüber dem rohen
customerId-Query-Param.

Docs: SECURITY-HARDENING.md § Runde 120 + docs/todo.md.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
2026-07-17 22:31:13 +02:00
co-authored by Claude Opus 4.7
parent d059912c1e
commit 23c530be46
3 changed files with 73 additions and 2 deletions
+40
View File
@@ -654,6 +654,46 @@ Modus hängt sowieso an einer schon validierten Email-Stammdate
---
## 🔒 Runde 120 KRITISCH: IDOR auf Vertragsbaum-Endpunkt (Live-Pentest-Fund)
**Finding (CRITICAL):** Live nachgewiesen ein Portal-User (Max,
`customerId=1`) konnte per
`GET /api/contracts?tree=true&customerId=2&includeDeactivated=true`
den vollständigen Vertragsbaum eines beliebigen Fremdkunden (Erika,
`customerId=2`) auslesen: fremder Name, Kundennummer, Vertragsnummern,
Provider und Tarife HTTP 200.
**Ursache:** Der `tree=true`-Zweig in `getContracts` returnte **früh**,
bevor die Portal-User-`customerIds`-Filterung griff, die für die flache
Vertragsliste weiter unten im selben Handler läuft. Der Zweig prüfte
schlicht nie, ob die angefragte `customerId` zum eingeloggten Portal-User
gehört. Vorbestehender Bug (nicht durch das R120-Feature „Deaktivierte
anzeigen" eingeführt) der neue `includeDeactivated`-Toggle hat ihn nur
sichtbarer gemacht, weil dann sogar archivierte Fremdverträge
mitkamen. Gegentest ohne den Param: identisches Leck.
**Fix:** `canAccessCustomer(req, res, customerId)` vor dem frühen Return
in [`contract.controller.ts`](../backend/src/controllers/contract.controller.ts).
Prüft eigene Customer-ID + vertretene Kunden MIT Live-Vollmacht
(`hasAuthorization`) und sendet selbst die 403. Nicht-Portal-User (Staff)
passieren unverändert. Gleiches Defense-in-Depth-Muster wie Pentest 56.3
bei `updateContract`/`deleteContract`.
**Zusatzbefund (Pentester):** In einem Staging-Vertrag lag ein
`<script>`-Test-Artefakt in `providerName`/`tariffName`. Nicht
exploitierbar keine der 9 `dangerouslySetInnerHTML`-Stellen im Frontend
rendert Vertrags-Provider-/Tarif-Namen, React escaped sie als Text.
Schreibpfad entschärft neue Werte zusätzlich per `sanitizeContractBody`
(`stripHtml`); der Altwert stammt aus DB-Direkteingabe.
**Lessons Learned (Pentester-seitig):** Der Fund verzögerte sich, weil
zunächst gegen den falschen Host getestet wurde
(`kundencenter.hacker-net.de` = PROD statt
`kundencenter-stage.stressfrei-wechseln.de`). Auf dem korrekten
Staging-Host reproduzierte sich das Finding sofort.
---
## 🔒 Runde 110 Mass-Assignment-Whitelist auf 7 Katalog-Endpunkten
**Finding (MEDIUM):** Der Pentester hat live nachgewiesen, dass