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:
@@ -970,12 +970,27 @@ export async function toggleMyAuthorization(req: AuthRequest, res: Response) {
|
||||
const representativeId = parseInt(req.params.representativeId);
|
||||
const { grant } = req.body;
|
||||
|
||||
// Vertreter-Name laden
|
||||
const representative = await prisma.customer.findUnique({
|
||||
where: { id: representativeId },
|
||||
select: { firstName: true, lastName: true },
|
||||
// Validierungen:
|
||||
// 1) Self-Grant verhindern (sinnlos und schafft Datenmüll).
|
||||
if (representativeId === user.customerId) {
|
||||
return res.status(400).json({ success: false, error: 'Kein Self-Grant möglich' });
|
||||
}
|
||||
// 2) Existenz + aktives Vertreter-Verhältnis in EINEM Lookup prüfen.
|
||||
// Beide Fälle (representative existiert nicht / keine aktive Beziehung)
|
||||
// geben identisch 403, damit ein Angreifer keine Customer-IDs aus der
|
||||
// DB enumerieren kann (kein 404-vs-403-Disclosure).
|
||||
const relation = await prisma.customerRepresentative.findFirst({
|
||||
where: { customerId: user.customerId, representativeId, isActive: true },
|
||||
include: { representative: { select: { firstName: true, lastName: true } } },
|
||||
});
|
||||
const repName = representative ? `${representative.firstName} ${representative.lastName}` : `#${representativeId}`;
|
||||
if (!relation) {
|
||||
return res.status(403).json({
|
||||
success: false,
|
||||
error: 'Kein Vertreter-Verhältnis – Vollmacht nicht erlaubt',
|
||||
});
|
||||
}
|
||||
|
||||
const repName = `${relation.representative.firstName} ${relation.representative.lastName}`;
|
||||
|
||||
let auth;
|
||||
if (grant) {
|
||||
@@ -991,10 +1006,9 @@ export async function toggleMyAuthorization(req: AuthRequest, res: Response) {
|
||||
res.json({ success: true, data: auth });
|
||||
} catch (error) {
|
||||
console.error('Fehler beim Ändern der Vollmacht:', error);
|
||||
res.status(400).json({
|
||||
success: false,
|
||||
error: error instanceof Error ? error.message : 'Fehler beim Ändern',
|
||||
});
|
||||
// Generische Fehlermeldung – Prisma-Errors enthalten Pfad/Schema und
|
||||
// sollten nicht an Endkunden geleakt werden.
|
||||
res.status(400).json({ success: false, error: 'Vollmacht konnte nicht aktualisiert werden' });
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user