Pentest R102: contractNumber-Whitelist nachgezogen
INFO-Finding: die interne CRM-Vertragsnummer war im Update-Path
nicht durch validateContractIdentifier abgedeckt. <script>… →
stripHtml → leerer String → Vertragsnummer silent überschrieben
(R87.1-Pattern auf einem sechsten Feld).
Fix: contractNumber zu CONTRACT_IDENTIFIER_FIELDS hinzugefügt.
Bestehende Whitelist (^[A-Za-z0-9_\-/. ]{0,100}$) und Raw-Input-
Validierung greifen automatisch. Legitime generierte Nummern
(GAS-MPVJ4P10FE8) bleiben OK, <script>/Overflow/CRLF/Tags → 400.
Kein Migration- oder Frontend-Change – der Fix nutzt den
bestehenden R86-Mechanismus.
Doku in SECURITY-HARDENING.md § Runde 102 und docs/todo.md.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -358,6 +358,14 @@ export function validateOptionalIsoDate(raw: unknown, fieldLabel: string): strin
|
|||||||
// Kunden-/Vertrags-Nummer und bleibt deutlich unter dem VARCHAR(191)-
|
// Kunden-/Vertrags-Nummer und bleibt deutlich unter dem VARCHAR(191)-
|
||||||
// Limit der DB-Spalte.
|
// Limit der DB-Spalte.
|
||||||
const CONTRACT_IDENTIFIER_FIELDS: ReadonlySet<string> = new Set([
|
const CONTRACT_IDENTIFIER_FIELDS: ReadonlySet<string> = new Set([
|
||||||
|
// Interne CRM-Vertragsnummer. Wird beim Create serverseitig generiert
|
||||||
|
// (`generateContractNumber`), aber der Update-Path filtert sie nicht
|
||||||
|
// aus – ein Admin (oder Angreifer mit users:update+contracts:update-
|
||||||
|
// JWT) könnte sie sonst überschreiben. Pentest 2026-07-04 INFO:
|
||||||
|
// `<script>` wurde durch stripHtml zu leerem String → silent Overwrite
|
||||||
|
// der Vertragsnummer. Whitelist analog zu den 5 Anbieter-/Vertriebs-
|
||||||
|
// Nummern-Feldern, damit der Pattern konsistent bleibt.
|
||||||
|
'contractNumber',
|
||||||
'customerNumberAtProvider',
|
'customerNumberAtProvider',
|
||||||
'contractNumberAtProvider',
|
'contractNumberAtProvider',
|
||||||
'orderNumberAtSalesPlatform',
|
'orderNumberAtSalesPlatform',
|
||||||
|
|||||||
@@ -654,6 +654,35 @@ Modus hängt sowieso an einer schon validierten Email-Stammdate
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
## 🔒 Runde 102 – Interne Vertragsnummer nachziehen
|
||||||
|
|
||||||
|
**Finding (INFO, kein Security-Impact ohne Admin-Login):**
|
||||||
|
|
||||||
|
`contractNumber` – die interne CRM-Vertragsnummer – hatte im
|
||||||
|
Update-Path keine Whitelist-Validierung. Sie wird beim Create
|
||||||
|
serverseitig via `generateContractNumber` erzeugt, aber
|
||||||
|
`updateContract` reicht den ganzen Body per `...contractData`
|
||||||
|
an Prisma durch. Ein Admin (oder Angreifer mit gestohlenem
|
||||||
|
`users:update`+`contracts:update`-JWT) konnte einen `<script>`-
|
||||||
|
Payload im `contractNumber`-Feld senden – `stripHtml` machte
|
||||||
|
daraus stille einen leeren String, die `@unique`-Constraint
|
||||||
|
akzeptierte den ersten leeren Eintrag → Vertragsnummer silent
|
||||||
|
überschrieben (R87.1-Pattern auf neuem Feld).
|
||||||
|
|
||||||
|
**Fix:** `contractNumber` zusätzlich in
|
||||||
|
`CONTRACT_IDENTIFIER_FIELDS` in
|
||||||
|
[`backend/src/utils/sanitize.ts`](../backend/src/utils/sanitize.ts).
|
||||||
|
Damit greift automatisch dieselbe Whitelist
|
||||||
|
`^[A-Za-z0-9_\-/. ]{0,100}$` gegen Raw-Input, die schon für die
|
||||||
|
fünf Anbieter-/Vertriebsplattform-Nummern läuft. Legitime
|
||||||
|
generierte Nummern (`GAS-MPVJ4P10FE8`) bleiben grün, `<script>`,
|
||||||
|
Overflow (>100), CRLF & Tag-Attribute → 400 mit klarer Meldung.
|
||||||
|
|
||||||
|
Kein separater Migration- oder Frontend-Change nötig – der Fix
|
||||||
|
nutzt den bestehenden R86-Mechanismus.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## 🧭 Wann ist „dicht" dicht?
|
## 🧭 Wann ist „dicht" dicht?
|
||||||
|
|
||||||
100 % gibt es nicht. Erreicht ist:
|
100 % gibt es nicht. Erreicht ist:
|
||||||
|
|||||||
@@ -97,6 +97,16 @@ isolierte Instanz (keine Multi-Tenancy im Code), Provisioning + Abrechnung
|
|||||||
|
|
||||||
## ✅ Erledigt
|
## ✅ Erledigt
|
||||||
|
|
||||||
|
- [x] **🔒 Pentest R102 – Interne Vertragsnummer nachziehen**
|
||||||
|
- INFO-Finding: `contractNumber` (interne CRM-Nummer) hatte im
|
||||||
|
Update-Path keine Whitelist. `<script>alert(1)</script>` →
|
||||||
|
`stripHtml` → leerer String → Vertragsnummer silent überschrieben.
|
||||||
|
- Fix: `contractNumber` zu `CONTRACT_IDENTIFIER_FIELDS` hinzugefügt –
|
||||||
|
bestehende R86-Whitelist (`^[A-Za-z0-9_\-/. ]{0,100}$`) greift
|
||||||
|
automatisch, R87-Fix (Raw-Input vor stripHtml) ist auch drin.
|
||||||
|
Legitime `GAS-MPVJ4P10FE8` bleibt OK, alle Angriffs-Payloads → 400.
|
||||||
|
- Doku in `SECURITY-HARDENING.md § Runde 102`.
|
||||||
|
|
||||||
- [x] **🔧 Pentest R101.1 – Inline-Preview-Pfad refaktoriert + Diagnose-Log**
|
- [x] **🔧 Pentest R101.1 – Inline-Preview-Pfad refaktoriert + Diagnose-Log**
|
||||||
- Pentester R101.1 (INFO/funktional) berichtet: `?disposition=inline`
|
- Pentester R101.1 (INFO/funktional) berichtet: `?disposition=inline`
|
||||||
bewirkt nichts, Browser zeigt Download-Dialog. Die Logik im
|
bewirkt nichts, Browser zeigt Download-Dialog. Die Logik im
|
||||||
|
|||||||
Reference in New Issue
Block a user