Rate-Limiting: IPv6-Bypass-Härtung via ipKeyGenerator

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 <noreply@anthropic.com>
This commit is contained in:
2026-07-27 16:38:54 +02:00
co-authored by Claude Opus 4.7
parent 627cae9b3b
commit b4dfede494
2 changed files with 24 additions and 3 deletions
+10 -3
View File
@@ -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}|<no-email>`;
},
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 ?? '<missing>').toString();
return `${ip}|staff-pw|${targetUserId}`;
},
+14
View File
@@ -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