fix(PROJ-64): Session-Invalidation bei Passwort-Change + Datei-Permissions gehärtet
Security-Audit deckte zwei Medium-Findings auf: JWTs blieben bis zu 8h nach Passwort-Change/-Reset oder Admin-TOTP-Reset gültig (kein Session-Invalidation), und archivierte Mails/Anhänge wurden mit 0644/0755 statt 0600/0700 geschrieben. - users.tokens_valid_after (neue Spalte) wird bei SetPassword() und InvalidateTokensBefore() gesetzt; ValidateToken() lehnt JWTs mit iat davor ab. - Admin-TOTP-Reset revoked jetzt aktive Sessions des Zielnutzers. - Mail-/Attachment-Dateien und ihre Verzeichnisse nur noch für den archivmail-Service-Account lesbar. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 5
parent
0ccbd5bafb
commit
b286352d07
+4
-3
@@ -63,10 +63,10 @@
|
||||
| PROJ-44 | OCR-GUI-Integration (Status, Download, Such-Highlight) | Deployed | [PROJ-44](PROJ-44-ocr-gui-integration.md) | 2026-05-08 |
|
||||
| PROJ-45 | IMAP Per-Folder UID-Tracking + UIDVALIDITY-Check | Deployed | [PROJ-45](PROJ-45-imap-folder-uid-tracking.md) | 2026-05-11 |
|
||||
| PROJ-46 | E-Mail als primärer Login-Identifier für Tenant-User | Planned | [PROJ-46](PROJ-46-email-login-tenant-user.md) | 2026-06-13 |
|
||||
| PROJ-47 | Tenant-Voll-Export per CLI | In Review | [PROJ-47](PROJ-47-tenant-voll-export-cli.md) | 2026-06-13 |
|
||||
| PROJ-47 | Tenant-Voll-Export per CLI | Deployed | [PROJ-47](PROJ-47-tenant-voll-export-cli.md) | 2026-06-13 |
|
||||
| PROJ-48 | Audit-Log Unveränderbarkeit (Nachbesserung PROJ-11) | Deployed | [PROJ-48](PROJ-48-audit-log-unveraenderbarkeit.md) | 2026-06-13 |
|
||||
| PROJ-49 | Verschlüsselungspflicht at-rest (Healthcheck & Warnung) | Deployed | [PROJ-49](PROJ-49-verschluesselungspflicht.md) | 2026-06-13 |
|
||||
| PROJ-50 | DSGVO-Löschersuchen für Mail-Inhalte (GoBD-Vorrang) | In Review | [PROJ-50](PROJ-50-dsgvo-loeschersuchen.md) | 2026-06-13 |
|
||||
| PROJ-50 | DSGVO-Löschersuchen für Mail-Inhalte (GoBD-Vorrang) | Deployed | [PROJ-50](PROJ-50-dsgvo-loeschersuchen.md) | 2026-06-13 |
|
||||
| PROJ-51 | Aufbewahrungsfristen nach Dokumentenart (Retention-Kategorien) | Deployed | [PROJ-51](PROJ-51-retention-kategorien.md) | 2026-06-13 |
|
||||
| PROJ-52 | Vollständigkeits-Reconciliation (Zähl-Report) | Planned | [PROJ-52](PROJ-52-vollstaendigkeits-reconciliation.md) | 2026-06-13 |
|
||||
| PROJ-53 | Konfigurierbare Listenanzahl pro Seite | Deployed | [PROJ-53](PROJ-53-konfigurierbare-listenanzahl.md) | 2026-06-14 |
|
||||
@@ -78,7 +78,8 @@
|
||||
| PROJ-61 | Fix Cross-Tenant Stored XSS via Mandanten-Logo (Sicherheitsbug) | Deployed | [PROJ-61](PROJ-61-fix-tenant-logo-xss-idor.md) | 2026-06-25 |
|
||||
| PROJ-62 | Fix Cross-Tenant IDOR bei POP3-Konto-Löschung/-Import (Sicherheitsbug) | Deployed | [PROJ-62](PROJ-62-fix-pop3-tenant-idor.md) | 2026-06-25 |
|
||||
| PROJ-63 | Defensive Tenant-Scope-Härtung der Tenant-Verwaltungs-Endpunkte (FUND-2) | Deployed | [PROJ-63](PROJ-63-harden-tenant-admin-scope.md) | 2026-06-25 |
|
||||
| PROJ-64 | Session-Invalidation bei Passwort-Change + Datei-Permissions-Härtung (Security-Audit) | Deployed | [PROJ-64](PROJ-64-session-invalidation-file-permissions.md) | 2026-07-03 |
|
||||
|
||||
<!-- Add features above this line -->
|
||||
|
||||
## Next Available ID: PROJ-64
|
||||
## Next Available ID: PROJ-65
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
# PROJ-64: Session-Invalidation bei Passwort-Change + Datei-Permissions-Härtung (Security-Audit-Nachbesserung)
|
||||
|
||||
## Status: Deployed
|
||||
**Created:** 2026-07-03
|
||||
**Last Updated:** 2026-07-03
|
||||
|
||||
## Hintergrund
|
||||
Vollständiges Codebase-Security-Audit (2026-07-03, 4 parallele Fokus-Reviews: Auth/Authz, Injection, Tenant-Isolation, Crypto/Storage) ergab zwei Medium-/Medium-High-Findings. Injection- und Tenant-Isolation-Review lieferten keine ausnutzbaren Befunde (bestehende Fixes aus PROJ-55/61/62/63 halten).
|
||||
|
||||
**Finding 1 — Kein Session-Invalidation bei Passwort-Change/Reset (Medium-High):**
|
||||
`SetPassword()` (Passwort-Change, Passwort-Reset) und Admin-TOTP-Reset aktualisierten nur den bcrypt-Hash bzw. TOTP-Status, invalidierten aber keine bereits ausgestellten JWTs. Die Token-Blacklist arbeitet nur per-JTI (Logout), es gab keinen "invalidate all sessions"-Mechanismus. Ein gestohlenes JWT/Cookie blieb bis zu 8h nach Passwort-Reset gültig — die Standard-Gegenmaßnahme "Passwort ändern, um Angreifer auszusperren" griff nicht.
|
||||
|
||||
**Finding 2 — Mail-Dateien world-readable (Medium):**
|
||||
`internal/storage/storage.go` und `attachments.go` schrieben archivierte Mails/Anhänge mit Mode `0644` (Verzeichnisse `0755`) — lesbar für jeden lokalen User/Group, nicht nur den archivmail-Service-Account. Falls Encryption nicht konfiguriert ist (dokumentierter Fallback), lag Mail-Inhalt im Klartext für jeden lokalen Account offen.
|
||||
|
||||
## Acceptance Criteria
|
||||
- [x] Neue Spalte `users.tokens_valid_after` (TIMESTAMPTZ, nullable), idempotent per `ALTER TABLE ... ADD COLUMN IF NOT EXISTS` in `initSchema`.
|
||||
- [x] `SetPassword()` setzt `tokens_valid_after = NOW()` bei jedem Passwort-Change/-Reset.
|
||||
- [x] Neue Methode `InvalidateTokensBefore(ctx, userID)` — von Admin-TOTP-Reset aufgerufen.
|
||||
- [x] `ValidateToken()` vergleicht JWT-Claim `iat` gegen `tokens_valid_after`; liegt `iat` davor, wird der Token als revoked abgelehnt (`auth: token revoked (credentials changed)`).
|
||||
- [x] Bestehendes Verhalten unverändert für User ohne gesetztes `tokens_valid_after` (NULL-Check, No-op).
|
||||
- [x] Mail-Dateien: `os.WriteFile` von `0o644` auf `0o600`.
|
||||
- [x] Attachment-Dateien: gleiche Änderung in `attachments.go`.
|
||||
- [x] Storage-Verzeichnisse (`store/`, `attachments/`, `meta/`, Shard-Verzeichnisse): `os.MkdirAll` von `0o755` auf `0o700`.
|
||||
- [x] Build- und Schema-Verifikation auf Testserver 192.168.1.132 (kein lokaler Go-Toolchain verfügbar).
|
||||
|
||||
## Implementierungsnotizen (2026-07-03)
|
||||
- `internal/userstore/userstore.go`: Spalte + `SetPassword()` erweitert + `InvalidateTokensBefore()` + `TokensValidAfter()` ergänzt.
|
||||
- `internal/auth/auth.go` (`ValidateToken`): `iat`-Vergleich gegen `tokens_valid_after` ergänzt, zusätzlicher DB-Roundtrip pro Request-Validierung (gleiche Größenordnung wie bestehender Blacklist-Check).
|
||||
- `internal/api/totp_handlers.go` (`handleTOTPReset`): Aufruf von `InvalidateTokensBefore()` nach erfolgreichem TOTP-Reset ergänzt (Fehler nur geloggt, kein Abbruch — Reset selbst war bereits erfolgreich).
|
||||
- `internal/api/onboarding_handlers.go` (Passwort-Reset) und `internal/api/profile_handlers.go` (Passwort-Change) benötigten keine Änderung — beide rufen `SetPassword()` auf, das die Invalidation jetzt intern miterledigt.
|
||||
- `internal/storage/storage.go`: Zeilen 72 (Init-MkdirAll), 413 (Shard-MkdirAll), 446 (WriteFile) gehärtet.
|
||||
- `internal/storage/attachments.go`: Zeilen 45 (MkdirAll), 49 (WriteFile) gehärtet.
|
||||
- Build (`CGO_ENABLED=0 go build -buildvcs=false`) und Schema-Migration (`tokens_valid_after`-Spalte, klaglos angelegt) auf 192.168.1.132 verifiziert, Dienste liefen danach unverändert weiter. Kein Produktiv-Deploy im Rahmen dieser Verifikation.
|
||||
|
||||
## Tech Design
|
||||
Übersprungen (additive Härtung, kein neues Subsystem — Muster analog zu bestehender Blacklist-/TOTP-Reset-Logik).
|
||||
|
||||
## Priorität
|
||||
Medium — kein akuter Exploit im laufenden Betrieb, aber schließt eine reale Lücke in der Incident-Response ("Passwort ändern" wirkte bisher nicht sofort) und eine konkrete Klartext-Offenlegung bei fehlender Encryption-Konfiguration.
|
||||
Reference in New Issue
Block a user