From b4dfede4949afb42142982c718b7903a3a0fac91 Mon Sep 17 00:00:00 2001 From: duffyduck Date: Mon, 27 Jul 2026 16:38:54 +0200 Subject: [PATCH] =?UTF-8?q?Rate-Limiting:=20IPv6-Bypass-H=C3=A4rtung=20via?= =?UTF-8?q?=20ipKeyGenerator?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit loginRateLimiter und staffPasswordReAuthLimiter keyten auf die volle req.ip. Bei IPv6 kann ein Angreifer aus seinem zugeteilten Block (/56–/64) pro Request eine neue Adresse nehmen und so das Per-IP-Limit umgehen – betrifft Login-Bruteforce-Schutz und den Passwort-Set- Reauth-Limiter. Fix: req.ip in beiden keyGenerator durch ipKeyGenerator() ersetzt (express-rate-limit v7). IPv6 wird auf das Subnetz normalisiert (Library-Default /56), IPv4 bleibt unverändert. Verifiziert: zwei verschiedene IPv6 im selben /56 ergeben denselben Key. Die Limiter ohne eigenen keyGenerator (Passwort-Reset, Consent) normalisieren IPv6 bereits über den Library-Default – damit jetzt konsistent. Ersetzt den verworfenen aria-WIP, neu auf aktuellem Stand gebaut. Co-Authored-By: Claude Opus 4.7 --- backend/src/middleware/rateLimit.ts | 13 ++++++++++--- docs/todo.md | 14 ++++++++++++++ 2 files changed, 24 insertions(+), 3 deletions(-) diff --git a/backend/src/middleware/rateLimit.ts b/backend/src/middleware/rateLimit.ts index 65bfc829..a194142e 100644 --- a/backend/src/middleware/rateLimit.ts +++ b/backend/src/middleware/rateLimit.ts @@ -6,7 +6,7 @@ * SecurityEvent (RATE_LIMIT_HIT) – damit der Monitoring-View und das * Alert-System sehen, wenn jemand auf die Tür hämmert. */ -import rateLimit from 'express-rate-limit'; +import rateLimit, { ipKeyGenerator } from 'express-rate-limit'; import { emit as emitSecurityEvent, contextFromRequest } from '../services/securityMonitor.service.js'; function onLimitReached(label: string, severity: 'MEDIUM' | 'HIGH') { @@ -54,7 +54,13 @@ export const loginRateLimiter = rateLimit({ skipSuccessfulRequests: true, keyGenerator: (req): string => { const email = (req.body?.email || '').toString().trim().toLowerCase(); - const ip = req.ip || 'unknown'; + // IPv6-Härtung: ipKeyGenerator normalisiert IPv6 auf das Subnetz + // (Default /56), damit ein Angreifer nicht durch Rotation innerhalb + // seines zugeteilten IPv6-Blocks das Per-IP-Limit umgeht. IPv4 bleibt + // unverändert. Die Limiter OHNE eigenen keyGenerator (Passwort-Reset, + // Consent) machen das schon über den Library-Default – hier holen wir + // die Custom-keyGenerator-Limiter auf denselben Stand. + const ip = ipKeyGenerator(req.ip || 'unknown'); return email ? `${ip}|${email}` : `${ip}|`; }, handler: (req, res, _next, options) => { @@ -107,7 +113,8 @@ export const staffPasswordReAuthLimiter = rateLimit({ }, skipSuccessfulRequests: true, keyGenerator: (req): string => { - const ip = req.ip || 'unknown'; + // IPv6-Härtung wie beim Login-Limiter (siehe dort). + const ip = ipKeyGenerator(req.ip || 'unknown'); const targetUserId = (req.params?.id ?? '').toString(); return `${ip}|staff-pw|${targetUserId}`; }, diff --git a/docs/todo.md b/docs/todo.md index dd7bd0d9..f67e646b 100644 --- a/docs/todo.md +++ b/docs/todo.md @@ -97,6 +97,20 @@ isolierte Instanz (keine Multi-Tenancy im Code), Provisioning + Abrechnung ## ✅ Erledigt +- [x] **🔒 Rate-Limiting: IPv6-Bypass-Härtung (ipKeyGenerator)** + - Die Rate-Limiter mit eigenem `keyGenerator` (`loginRateLimiter`, + `staffPasswordReAuthLimiter`) keyten auf die **volle** `req.ip`. + Bei IPv6 kann ein Angreifer aus seinem zugeteilten Block (/56–/64) + pro Versuch eine neue Adresse nehmen und so das Per-IP-Limit + (Login-Bruteforce, Passwort-Set-Reauth) umgehen. + - Fix: `req.ip` in beiden `keyGenerator` durch `ipKeyGenerator(...)` + (express-rate-limit v7) ersetzt → IPv6 wird auf das Subnetz + normalisiert (Library-Default /56), IPv4 unverändert. Verifiziert: + zwei verschiedene IPv6 im selben /56 ⇒ derselbe Key. + - Die Limiter OHNE eigenen keyGenerator (Passwort-Reset, Consent) + machten das schon über den Library-Default – jetzt konsistent. + (Ersetzt den verworfenen aria-WIP; auf aktuellem Stand neu gebaut.) + - [x] **🐞 Spam-Tab: Anhänge aus Junk-Ordner (Pentest R124-Fund)** - Beim Spam-Feature wurden `moveEmailToTrash`/`restoreEmailFromTrash` auf den echten Junk-Pfad umgestellt, aber vier Attachment-Funktionen