Globaler /api-Rate-Limit-Backstop (Pentest R148)
Es gab bislang keinen generellen Limiter - nur Login/Passwort-Reset/ Staff-ReAuth/Consent; authentifizierte Endpoints waren gegen Enumeration/ DoS ungedrosselt. Neuer apiBackstopRateLimiter auf alle /api-Requests, vor den Routern gemountet (ergaenzt die feineren Limiter, ersetzt sie nicht). Key = nur IPv6-/56-normalisierte IP - bewusst nicht IP+User, da der User- Claim hier nur unverifiziert lesbar waere (authenticate laeuft erst pro Route) und ein Angreifer sonst per Fake-userId beliebig Buckets erzeugen koennte. Limit per Env API_RATE_LIMIT_PER_MIN (Default 1200/min, Floor 60), /api/health ausgenommen. Kein SecurityEvent pro Block (Flood-Amplification). Deckt zugleich den offenen IPv6-Rate-Limit-Test ausserhalb der Auth-Pfade ab. Verifiziert: 60x200 dann 429, health bleibt 200. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -30,6 +30,7 @@ if (!process.env.DATABASE_URL && process.env.DB_USER && process.env.DB_PASSWORD
|
|||||||
process.env.DATABASE_URL = `mysql://${u}:${p}@${h}:${port}/${process.env.DB_NAME}`;
|
process.env.DATABASE_URL = `mysql://${u}:${p}@${h}:${port}/${process.env.DB_NAME}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
import { apiBackstopRateLimiter } from './middleware/rateLimit.js';
|
||||||
import authRoutes from './routes/auth.routes.js';
|
import authRoutes from './routes/auth.routes.js';
|
||||||
import customerRoutes from './routes/customer.routes.js';
|
import customerRoutes from './routes/customer.routes.js';
|
||||||
import addressRoutes from './routes/address.routes.js';
|
import addressRoutes from './routes/address.routes.js';
|
||||||
@@ -346,6 +347,11 @@ app.use('/api', (req, res, next) => {
|
|||||||
next();
|
next();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Globaler Backstop-Rate-Limiter für ALLE /api-Requests (Pentest R148).
|
||||||
|
// Großzügige Obergrenze pro IP – ergänzt die feineren Limiter (Login etc.),
|
||||||
|
// die als erste greifen. Siehe middleware/rateLimit.ts für die Begründung.
|
||||||
|
app.use('/api', apiBackstopRateLimiter);
|
||||||
|
|
||||||
// Öffentliche Routes (OHNE Authentifizierung)
|
// Öffentliche Routes (OHNE Authentifizierung)
|
||||||
app.use('/api/public/consent', consentPublicRoutes);
|
app.use('/api/public/consent', consentPublicRoutes);
|
||||||
|
|
||||||
|
|||||||
@@ -132,6 +132,47 @@ export const staffPasswordReAuthLimiter = rateLimit({
|
|||||||
* (Pentest 2026-05-20 INFO 28.4). 30 Requests pro 15 min pro IP reicht
|
* (Pentest 2026-05-20 INFO 28.4). 30 Requests pro 15 min pro IP reicht
|
||||||
* für legitime Kunden weit aus.
|
* für legitime Kunden weit aus.
|
||||||
*/
|
*/
|
||||||
|
/**
|
||||||
|
* Globaler Backstop-Limiter für ALLE /api-Requests (Pentest R148).
|
||||||
|
*
|
||||||
|
* Hintergrund: Es gab bislang KEINEN generellen /api-Limiter – nur die
|
||||||
|
* dedizierten oben (Login, Passwort-Reset, Staff-Re-Auth, Consent). Damit war
|
||||||
|
* jeder authentifizierte Endpoint gegen Enumeration/Scripted-Abuse/DoS
|
||||||
|
* ungedrosselt. Dieser Limiter ist eine großzügige Obergrenze, KEIN Ersatz für
|
||||||
|
* die feineren Limiter (die feuern früher und bleiben aktiv).
|
||||||
|
*
|
||||||
|
* Key = NUR die (IPv6-/56-normalisierte) IP. Bewusst NICHT (IP+User): den
|
||||||
|
* User-Claim könnten wir hier nur unverifiziert aus dem Token lesen (die volle
|
||||||
|
* `authenticate`-Prüfung inkl. DB läuft erst pro Route). Ein Angreifer könnte
|
||||||
|
* dann mit gefälschten userId-Claims beliebig frische Buckets erzeugen und den
|
||||||
|
* Backstop umgehen. Per-Account-Präzision liefern ohnehin die Login-Limiter.
|
||||||
|
*
|
||||||
|
* Limit per Env `API_RATE_LIMIT_PER_MIN` (Default 1200/min ≈ 20/s pro IP) –
|
||||||
|
* für legitime Nutzung (auch mehrere Nutzer hinter NAT) weit ausreichend,
|
||||||
|
* bremst aber Flooding massiv. Healthcheck (`/api/health`) ist ausgenommen.
|
||||||
|
*
|
||||||
|
* KEIN SecurityEvent pro geblocktem Request: Bei einem Flood würde das den
|
||||||
|
* Security-/Audit-Store selbst zumüllen (Amplification). Die 429 stehen im
|
||||||
|
* Access-Log; die feineren Limiter melden weiterhin an das Monitoring.
|
||||||
|
*/
|
||||||
|
const API_RATE_LIMIT_PER_MIN = Math.max(
|
||||||
|
parseInt(process.env.API_RATE_LIMIT_PER_MIN || '', 10) || 1200,
|
||||||
|
60,
|
||||||
|
);
|
||||||
|
|
||||||
|
export const apiBackstopRateLimiter = rateLimit({
|
||||||
|
windowMs: 60 * 1000,
|
||||||
|
limit: API_RATE_LIMIT_PER_MIN,
|
||||||
|
standardHeaders: 'draft-7',
|
||||||
|
legacyHeaders: false,
|
||||||
|
message: {
|
||||||
|
success: false,
|
||||||
|
error: 'Zu viele Anfragen in kurzer Zeit. Bitte einen Moment warten.',
|
||||||
|
},
|
||||||
|
keyGenerator: (req): string => ipKeyGenerator(req.ip || 'unknown'),
|
||||||
|
skip: (req) => req.path === '/health',
|
||||||
|
});
|
||||||
|
|
||||||
export const publicConsentRateLimiter = rateLimit({
|
export const publicConsentRateLimiter = rateLimit({
|
||||||
windowMs: 15 * 60 * 1000,
|
windowMs: 15 * 60 * 1000,
|
||||||
limit: 30,
|
limit: 30,
|
||||||
|
|||||||
@@ -97,6 +97,23 @@ isolierte Instanz (keine Multi-Tenancy im Code), Provisioning + Abrechnung
|
|||||||
|
|
||||||
## ✅ Erledigt
|
## ✅ Erledigt
|
||||||
|
|
||||||
|
- [x] **🛡️ Globaler API-Rate-Limit-Backstop + BLZ-Guard-Härtung (Pentest R148)** (2026-08-12)
|
||||||
|
- **Backstop:** Neuer genereller Limiter auf ALLE `/api`-Requests
|
||||||
|
(`apiBackstopRateLimiter`, in `index.ts` vor den Routern). Vorher gab es
|
||||||
|
KEINEN generellen Limiter – nur Login/Passwort-Reset/Staff-ReAuth/Consent;
|
||||||
|
authentifizierte Endpoints waren gegen Enumeration/DoS ungedrosselt.
|
||||||
|
Key = **nur IPv6-/56-normalisierte IP** (bewusst nicht IP+User: der
|
||||||
|
User-Claim wäre hier nur unverifiziert lesbar → Bypass per Fake-Token).
|
||||||
|
Limit per Env `API_RATE_LIMIT_PER_MIN` (Default **1200/min**, Floor 60),
|
||||||
|
`/api/health` ausgenommen. Kein SecurityEvent pro Block (sonst Flood-
|
||||||
|
Amplification). Verifiziert (60×200→429, health bleibt 200).
|
||||||
|
- **Deckt den offenen IPv6-Test mit ab:** außerhalb der Auth-Pfade greift jetzt
|
||||||
|
ebenfalls ein IPv6-normalisierter Limiter.
|
||||||
|
- **BLZ-Poisoning-Guard:** prüft jetzt JEDEN Dataset-Eintrag (nicht nur die
|
||||||
|
erste Zeile) – 8-stellige BLZ, Wert `[Name]` oder `[Name,BIC]`,
|
||||||
|
`next.remove` = BLZ-Liste, `next.valid` = gültiges Datum. Banken **ohne BIC**
|
||||||
|
(`[Name]`, z.B. BLZ 60050009) korrekt zugelassen → `lookupBlz` liefert `bic:''`.
|
||||||
|
|
||||||
- [x] **🔄 BLZ-/Bankdaten: Auto-Update via Volume + Einstellungen** (2026-08-12)
|
- [x] **🔄 BLZ-/Bankdaten: Auto-Update via Volume + Einstellungen** (2026-08-12)
|
||||||
- Neue Einstellungen-Seite **Einstellungen → Bankdaten (BLZ)** (`/settings/bank-data`):
|
- Neue Einstellungen-Seite **Einstellungen → Bankdaten (BLZ)** (`/settings/bank-data`):
|
||||||
zeigt Datenstand (aktive Quelle Volume/Image, Version, Anzahl Banken,
|
zeigt Datenstand (aktive Quelle Volume/Image, Version, Anzahl Banken,
|
||||||
|
|||||||
Reference in New Issue
Block a user