duffyduck
69b9a35674
Security-Hardening Runde 11: Pentest Runde 7 (Portal-PW + Download-Tokens)
...
Hit-List vom Pentester abgearbeitet. Hauptpunkte:
1) Contract/Mail-Credentials (password/internet/sip/simcard, mailbox/send/
reset-password): ALLE bereits durch canAccess* gesichert, keine Lücke.
2) GET /customers/:id/portal/password (Klartext-Portal-PW-Abruf):
fehlender canAccessCustomer-Check ergänzt. Defense in depth gegen
versehentliche customers:update-Permission an Portal/eingeschränkte
Mitarbeiter.
3) Admin-Endpoints (factory-reset, developer/*, audit-logs/rehash,
audit-logs/customer): durch admin-Permissions geschützt – Portal-User
haben diese nicht.
4) Token-in-URL (NIEDRIG): Langlebige Access-JWTs landeten als ?token= in
URLs für iframe-PDFs, Audit-Export-Window etc. → nginx-Logs +
Browser-History + Referer.
Lösung: kurzlebige Download-Tokens.
- signDownloadToken() liefert JWT mit type='download', exp=60s
- Auth-Middleware akzeptiert type='download' AUSSCHLIESSLICH via
?token=, niemals als Bearer-Header
- POST /api/auth/download-token Endpoint (authenticated)
- Frontend: authApi.getDownloadToken() utility
- 4 Stellen migriert: AuditLog-Export, PdfTemplate-Preview-iframe,
PdfTemplate-Generate, ContractDetail-PDF-Generate (2x),
Portal-Privacy-PDF
- fileUrl/getAttachmentUrl sind synchron + breit gestreut – Migration
bleibt für Folge-PR
Live-verifiziert:
- Download-Token: 1773 Zeichen, type=download, exp-iat=60s
- als Header → 401 (Falscher Token-Typ), als ?token= → 200
- portal-user (Customer 3) auf customers/2/portal/password → 403
Rate-Limiter-Check: express-rate-limit Fixed-Window, kein Reset bei jedem
Request (Pentester-Klage „Fenster reseted sich" stimmt mit dem Code nicht
überein – wahrscheinlich Retry-After-Misinterpretation). Kein Code-Bug
identifiziert; ggf. später Admin-Override-Endpoint nachrüsten.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com >
2026-05-17 00:40:00 +02:00
duffyduck
9830ac29a5
security: JWT raus aus localStorage – Refresh-Cookie-Pattern für SPA
...
Behebt das Pentest-Finding „JWT in localStorage (MITTEL)": bei XSS war
der Token JS-erreichbar → Angreifer könnte alle Anbieter-Credentials
abrufen. Branchenstandard-Lösung für SPAs jetzt umgesetzt.
Architektur:
- Access-Token: 15 min Lifetime, lebt NUR im JavaScript-Memory
(api.ts tokenStore + AuthContext). Kein localStorage mehr.
- Refresh-Token: 7 Tage, im httpOnly-Cookie (Secure bei HTTPS_ENABLED,
SameSite=Strict, Path=/api/auth). JavaScript hat keinen Zugriff →
XSS klaut max. einen 15-min-Access-Token.
Backend:
- signAccessToken/signRefreshToken mit `type`-Claim
- Auth-Middleware verweigert Tokens mit type=refresh
- POST /api/auth/login + /customer-login: setzt refresh_token-Cookie,
gibt access-Token im Body
- POST /api/auth/refresh: liest Cookie, rotiert ihn, gibt neuen Access
aus. Prüft tokenInvalidatedAt (Logout/Rollenänderung = sofortige
Invalidation auch des Refresh-Tokens)
- POST /api/auth/logout: löscht Cookie + setzt tokenInvalidatedAt
- cookie-parser als neue Dependency
Frontend:
- api.ts: in-memory tokenStore (kein localStorage); withCredentials=true
für Cookie-Roundtrip; axios-Response-Interceptor mit
Single-Flight-Refresh-Retry bei 401 (Original-Request wird
transparent retried mit neuem Token)
- AuthContext: beim App-Start /auth/refresh aufrufen → wenn Cookie
noch gültig, ist der User automatisch eingeloggt. Tab-Reload
funktioniert weiterhin obwohl Access-Token nur in memory ist.
- 9 alte `localStorage.getItem('token')`-Stellen migriert auf
`getAccessToken()` (PDF-Preview-iframe, Audit-Log-CSV-Export,
DB-Backup-Download, File-Download-URLs, Portal-PDF-Link)
Live verifiziert:
- Login setzt Cookie (httpOnly, SameSite=Strict, Path=/api/auth) + Bearer
- API mit Bearer: 200; ohne: 401
- Refresh mit Cookie: rotiert sauber + neuer Access-Token im Body
- Refresh-Token als Bearer abgewiesen: 401 ("Falscher Token-Typ")
- Logout: Cookie gelöscht, danach /refresh → 401
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com >
2026-05-16 16:06:17 +02:00
duffyduck
0cf3dd6a7b
Security-Hardening Runde 10: Security-Monitoring + Alerting
...
Defense-in-Depth für alles, was in den ersten 9 Runden nicht durch Code
verhindert wurde: zumindest gesehen + alarmiert werden.
📊 SecurityEvent-Tabelle (Prisma)
- Type/Severity/IP/User/Endpoint + Indexen für Filter+Threshold-Detection
- Trennt sich vom AuditLog: AuditLog ist forensisch + hash-gekettet,
SecurityEvent ist optimiert für Realtime-Alerting + Aggregation.
🪝 Hooks an kritischen Stellen
- Login (Success/Failed) – auth.controller
- Logout, Password-Reset (Request + Confirm) – auth.controller
- Rate-Limit-Hit – middleware/rateLimit
- IDOR-403 – utils/accessControl (canAccessCustomer / canAccessContract)
- SSRF-Block – emailProvider.controller (test-connection + test-mail-access)
- JWT-Reject (alg=none, expired, manipuliert) – middleware/auth
🚨 Threshold-Detection + Alerting (securityAlert.service.ts)
- Cron jede Minute: prüft Brute-Force-Patterns je IP
- 10× LOGIN_FAILED in 60 min → CRITICAL Brute-Force-Verdacht
- 5× ACCESS_DENIED in 5 min → CRITICAL IDOR-Probing-Verdacht
- 3× SSRF_BLOCKED in 60 min → CRITICAL SSRF-Probing
- 3× TOKEN_REJECTED HIGH in 5 min → CRITICAL JWT-Manipulation
- CRITICAL-Events: Sofort-Alert per E-Mail (debounced)
- Cron stündlich: Digest mit HIGH+MEDIUM-Events (wenn aktiviert)
- Sofort-Alert + Digest laufen über System-E-Mail-Provider
(gleicher Pfad wie Geburtstagsgrüße, Passwort-Reset)
🖥 Frontend: Settings → "Sicherheits-Monitoring"
- Alert-E-Mail-Adresse + Digest-Toggle
- Test-Alert-Button + Digest-jetzt-Button
- Stats-Cards pro Severity (CRITICAL/HIGH/MEDIUM/LOW/INFO)
- Filter (Type/Severity/Search/IP) + Pagination
- Auto-Refresh alle 30 s
- Verlinkt aus Settings-Übersicht (settings:read Permission)
🧪 Live-verifiziert
- Login-Fehlversuch → LOGIN_FAILED Event
- Portal probt 4× fremde Customer-IDs → 4× ACCESS_DENIED
- SSRF-Probe (169.254.169.254) → SSRF_BLOCKED Event
- 12× LOGIN_FAILED simuliert → Cron erzeugt CRITICAL nach ≤60s
- CRITICAL-Sofort-Alert binnen 30s zugestellt
- Test-Alert-Button: E-Mail zugestellt
- Hourly-Digest mit 5 Events: E-Mail mit Tabelle zugestellt
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com >
2026-05-01 09:25:47 +02:00
duffyduck
8aead8c2f6
Security-Hardening Runde 3: JWT, trust-proxy, weitere IDORs, Attachment-Härtung
...
- JWT-Algorithmus fest auf HS256 (Defense-in-Depth gegen alg-confusion)
- app.set('trust proxy', 1) – Rate-Limiter wirkt jetzt auch hinter Reverse-Proxy
- IDOR-Fix: Invoice-ECD-Endpoints + PDF-Template-Generierung (canAccessContract/ECD)
- Email-Anhang-Download: Content-Type-Safelist, SVG nie inline, nosniff, Filename-CRLF-Sanitize
- Provider/Tariff-GET-Routen: requirePermission('providers:read') (Portal-Kunden raus)
- SMTP-Header-Injection zentral in sendEmail blockiert (schützt alle Caller)
- bcrypt-Cost 10 → 12 (OWASP 2026)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com >
2026-04-24 09:38:25 +02:00
duffyduck
1c46d7345c
Security-Hardening: IDOR-Fixes, XSS-Sanitizer, CORS+Helmet, Data-Exposure
...
Umfassender Security-Review vor öffentlichem Deployment.
Detaillierter Report in docs/SECURITY-REVIEW.md.
🔴 KRITISCHE FIXES:
1. CORS offen → jetzt nur explizite Origins (via CORS_ORIGINS env),
in Production per default komplett aus (gleiche Origin erzwingt Browser).
2. Keine Security-Headers → helmet-Middleware hinzugefügt.
X-Frame-Options, X-Content-Type-Options, HSTS, Referrer-Policy, CORP.
3. JWT-Fallback-Secret entfernt. Beim Server-Start wird jetzt geprüft ob
JWT_SECRET (min 32 Zeichen) und ENCRYPTION_KEY (exakt 64 Hex) gesetzt sind,
sonst Fail-Fast mit klarer Fehlermeldung.
4. IDOR bei 7 Contract-Endpoints. Portal-Kunden mit 'contracts:read'
konnten über geratene IDs fremde Daten abrufen (Passwort, SIM-PIN/PUK,
Internet-Zugangsdaten, SIP-Credentials, Vertragsdokumente, Rechnungen).
Neuer Helper canAccessContract() in utils/accessControl.ts in allen
betroffenen Endpoints eingebaut. Prüft Vertrag-Besitzer + Vollmachten.
5. XSS via Email-Body. email.htmlBody wurde ungefiltert via
dangerouslySetInnerHTML gerendert. Angreifer konnte Mail mit <script>
schicken → Token-Diebstahl aus localStorage. Jetzt mit DOMPurify
sanitized: verbietet script/iframe/form/inline-handler, erlaubt
normale Formatierung + Bilder.
6. Customer-API leakte sensible Felder:
- portalPasswordHash (bcrypt-Hash)
- portalPasswordEncrypted (symmetrisch, mit ENCRYPTION_KEY entschlüsselbar)
- portalPasswordResetToken (gültig 2h)
Neuer Sanitizer in utils/sanitize.ts, angewendet in getCustomer/getCustomers.
Admin mit customers:update darf portalPasswordEncrypted sehen (für UI-Anzeige),
alle anderen Rollen nicht.
🟡 WICHTIGE FIXES:
7. Portal-JWT-Invalidation nach Passwort-Reset. Neues Feld
Customer.portalTokenInvalidatedAt, wird beim Reset auf now() gesetzt.
Auth-Middleware prüft Portal-Sessions dagegen. Alte Sessions werden
dadurch invalidiert.
8. express.json() mit 5 MB Size-Limit (statt Default 100 KB unklar).
Neue Files:
- backend/src/utils/accessControl.ts - IDOR-Schutz
- backend/src/utils/sanitize.ts - Response-Sanitizer
- docs/SECURITY-REVIEW.md - vollständiger Report + Deployment-Checkliste
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com >
2026-04-23 22:06:16 +02:00
duffyduck
fd55742c57
complete new audit system
2026-03-21 18:23:54 +01:00
duffyduck
c3edb8ad2e
gdpr audit implemented, email log, vollmachten, pdf delete cancel data privacy and vollmachten, removed message no id card in engergy car, and other contracts that are not telecom contracts, added insert counter for engery
2026-03-21 11:59:53 +01:00
duffyduck
8c9e61cf17
added backup and email client
2026-02-01 00:02:35 +01:00
Stefan Hacker
e209e9bbca
first commit
2026-01-29 01:16:54 +01:00