Security-Hardening Runde 7: SSRF-Schutz + Logout-Endpoint

🛡 SSRF-Schutz in test-connection / test-mail-access
- Admin-User konnte über apiUrl bzw. SMTP/IMAP-Server-Felder
  Connections zu Cloud-Metadata-Endpoints (169.254.169.254,
  metadata.google.internal etc.) auslösen. Internal-Port-Scan
  über Timing-Differenzen war messbar.
- Fix: neuer utils/ssrfGuard.ts blockiert pre-flight 169.254.0.0/16,
  0.0.0.0/8, Multicast/Reserved-Ranges, AWS-IPv6-Metadata,
  IPv6-Link-Local und Cloud-Metadata-Hostnames.
  Loopback (127.0.0.0/8) bleibt erlaubt – legitime Plesk/Postfix-
  Setups sollen weiter funktionieren.

🔒 Logout-Endpoint POST /api/auth/logout
- Setzt tokenInvalidatedAt / portalTokenInvalidatedAt auf jetzt.
  Auth-Middleware prüft das Feld bereits und lehnt Tokens mit
  iat davor ab. Ohne diesen Endpoint blieb ein "abgemeldeter"
  JWT bis Expiry (7d) gültig.

Live-verifiziert:
- 169.254.169.254 / metadata.google.internal / 0.0.0.0 → 400
- 127.0.0.1 (Plesk-Fall) weiter erlaubt
- /me vor Logout 200, nach Logout 401 "Sitzung ungültig"

Geprüft + sauber (Runde 7, kein Bug):
- Public Consent (122-bit Random-UUID nicht brute-force-bar)
- Magic-Bytes-Bypass beim Upload
- PDF manualValues Injection (keine HTML-Render-Surface)
- Query-Filter-Override (?customerId=X) – vom Portal-Filter ignoriert
- Audit-Logs / Email-Config / Backup-Endpoints als Portal: 403

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-01 07:47:26 +02:00
parent 0c0cecdbbd
commit df6eb9724d
5 changed files with 158 additions and 0 deletions
+34
View File
@@ -141,6 +141,40 @@ isolierte Instanz (keine Multi-Tenancy im Code), Provisioning + Abrechnung
- Provider/Tariff-GETs: `requirePermission('providers:read')` (Portal-Kunden sehen Provider-Liste nicht mehr)
- SMTP-Header-Injection: zentrale CRLF-Validierung in `smtpService.sendEmail` (schützt alle Caller)
- bcrypt cost 10 → 12 (OWASP 2026)
- **Runde 7 Letzter Schliff (SSRF + Logout):**
- **SSRF-Schutz** in `test-connection` und `test-mail-access`: ein
Admin-User konnte über die Plesk-API-URL bzw. SMTP/IMAP-Server-Felder
Connections zu beliebigen IPs auslösen (Cloud-Metadata-Endpoints,
Link-Local, AWS/GCP-Metadata-Hosts). Internal-Port-Scanning via
Timing-Differenzen war messbar (22/80/3306/5432/6379 unterschiedlich).
Fix: neuer Helper `utils/ssrfGuard.ts` blockiert vor jeder ausgehenden
Verbindung 169.254.0.0/16, 0.0.0.0/8, Multicast/Reserved-Ranges,
AWS-IPv6-Metadata, IPv6-Link-Local und bekannte Cloud-Metadata-
Hostnames (metadata.google.internal etc.). Loopback (127.0.0.0/8)
bleibt erlaubt für legitime Plesk/Postfix-Setups.
- **Logout-Endpoint** `POST /api/auth/logout`: setzt
`tokenInvalidatedAt` / `portalTokenInvalidatedAt` auf jetzt. Auth-
Middleware prüft das Feld und lehnt Tokens mit `iat` davor ab.
JWTs sind stateless ohne diesen Mechanismus bleibt ein
„abgemeldeter" Token bis zum natürlichen Expiry (7d) gültig.
- Live-verifiziert: 169.254.169.254/metadata.google.internal/0.0.0.0
werden mit 400 abgelehnt; 127.0.0.1 weiter erlaubt; Logout
invalidiert den Token sofort (HTTP 401 „Sitzung ungültig").
**Geprüft + sauber (Runde 7):**
- Public Consent (random Hash → 404, kein Brute-Force durch 122-bit-UUID)
- Magic-Bytes-Bypass beim Upload (HTML als image/png) → blockiert
- PDF-Generation mit injizierten manualValues → kein XSS-Vektor (PDFs sind keine Web-Renderer)
- Audit-Logs für Portal-User: 403
- Email-Config-Update als Portal: 403
- Backup-Endpoints als Portal: 403
- Query-Filter-Override (?customerId=X) → vom Portal-Filter ignoriert
**Bewusst NICHT gefixt (zu invasiv für v1.0):**
- Vollständige DNS-Resolution beim SSRF-Guard (gegen DNS-Rebinding)
kann legitimes CDN/Caching brechen, v1.1-Item.
- Per-File-Ownership-Check bei `/api/uploads` (siehe Runde 5).
- **Runde 6 Tiefer Live-Pentest (auf Wunsch des Users, „bevor andere es tun"):**
- 🚨 **`GET /api/customers` leakte als Portal-User die komplette
Kundendatenbank** (alle Namen, E-Mails, customerNumber etc.). Der