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:
@@ -10,7 +10,7 @@ import { ApiResponse, AuthRequest } from '../types/index.js';
|
||||
import { logChange } from '../services/audit.service.js';
|
||||
import { sanitizeContract, sanitizeContractStrict, sanitizeContracts, sanitizeContractsStrict, stripHtml, sanitizeNotes, validateContractDocumentType, validateOptionalIsoDate, isContractIdentifierField, validateContractIdentifier, validatePortalUsername } from '../utils/sanitize.js';
|
||||
import { ApiError } from '../utils/apiError.js';
|
||||
import { canAccessContract } from '../utils/accessControl.js';
|
||||
import { canAccessContract, canAccessCustomer } from '../utils/accessControl.js';
|
||||
import { maybeActivateOnDeliveryConfirmation, withContractDocumentLock } from '../services/contractStatusScheduler.service.js';
|
||||
|
||||
/**
|
||||
@@ -78,8 +78,18 @@ export async function getContracts(req: AuthRequest, res: Response): Promise<voi
|
||||
|
||||
// Baumstruktur für Kundenansicht
|
||||
if (tree === 'true' && customerId) {
|
||||
const customerIdNum = parseInt(customerId as string);
|
||||
// Pentest R120 (CRITICAL IDOR): Der tree=true-Zweig returnte früh,
|
||||
// BEVOR die Portal-User-customerIds-Filterung unten für die flache
|
||||
// Liste griff. Ein Portal-User konnte damit den vollständigen
|
||||
// Vertragsbaum eines beliebigen Fremdkunden lesen
|
||||
// (?tree=true&customerId=<fremd>). Ownership-Check nachgezogen –
|
||||
// canAccessCustomer prüft eigene ID + vertretene MIT Live-Vollmacht
|
||||
// und sendet selbst die 403-Response. Nicht-Portal-User (Staff)
|
||||
// passieren unverändert.
|
||||
if (!(await canAccessCustomer(req, res, customerIdNum))) return;
|
||||
const treeData = await contractService.getContractTreeForCustomer(
|
||||
parseInt(customerId as string),
|
||||
customerIdNum,
|
||||
includeDeactivated === 'true',
|
||||
);
|
||||
res.json({ success: true, data: treeData } as ApiResponse);
|
||||
|
||||
Reference in New Issue
Block a user