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:
+16
-6
@@ -58,10 +58,15 @@ const app = express();
|
||||
const PORT = process.env.PORT || 3001;
|
||||
|
||||
// Hinter einem Reverse-Proxy (Nginx/Plesk) läuft der Server typisch auf localhost.
|
||||
// `trust proxy = 1` = dem ersten Hop X-Forwarded-For vertrauen (damit req.ip
|
||||
// die echte Client-IP ist). Wichtig für express-rate-limit, sonst teilen sich
|
||||
// alle Requests dieselbe Proxy-IP und das Rate-Limit ist unwirksam.
|
||||
app.set('trust proxy', 1);
|
||||
// `trust proxy = 'loopback'` vertraut nur Connections von 127.0.0.1 / ::1
|
||||
// (= lokaler Reverse-Proxy). Damit kann ein Angreifer mit DIREKTEM Zugriff
|
||||
// auf das Backend nicht via X-Forwarded-For den Rate-Limiter umgehen,
|
||||
// während gleichzeitig der lokale Reverse-Proxy die echte Client-IP liefern darf.
|
||||
//
|
||||
// WICHTIG für Production: Backend nur auf 127.0.0.1 lauschen lassen
|
||||
// (LISTEN_ADDR=127.0.0.1) – sonst kann ein direkter Connect von außen
|
||||
// trotzdem als loopback gelten, falls das Routing das so durchstellt.
|
||||
app.set('trust proxy', 'loopback');
|
||||
|
||||
// ==================== SECURITY MIDDLEWARE ====================
|
||||
|
||||
@@ -174,8 +179,13 @@ app.use((err: Error & { status?: number; type?: string }, req: express.Request,
|
||||
res.status(status).json({ success: false, error: message });
|
||||
});
|
||||
|
||||
app.listen(PORT, () => {
|
||||
console.log(`Server läuft auf Port ${PORT}`);
|
||||
// Listen-Adresse: in Production typischerweise 127.0.0.1 (nur lokaler
|
||||
// Reverse-Proxy soll connecten dürfen). LISTEN_ADDR per Env überschreibbar.
|
||||
const LISTEN_ADDR = process.env.LISTEN_ADDR
|
||||
|| (process.env.NODE_ENV === 'production' ? '127.0.0.1' : '0.0.0.0');
|
||||
|
||||
app.listen(PORT as number, LISTEN_ADDR, () => {
|
||||
console.log(`Server läuft auf ${LISTEN_ADDR}:${PORT}`);
|
||||
// Hintergrund-Scheduler (Geburtstagsgrüße etc.) starten
|
||||
startBirthdayScheduler();
|
||||
startContractStatusScheduler();
|
||||
|
||||
Reference in New Issue
Block a user