Files
MABEA/backend/THREAT_MODEL_BACKEND.md
patrickandClaude Sonnet 5 230237ac94
CI / backend-tests (push) Successful in 3m16s
CI / frontend-build (push) Successful in 20s
feat(flutter): Mangelmeldung-Offline-Queue + Threat-Model-Fund als Kachel
Android-Client zieht die Draft-first-Offline-Queue für Mangelmeldung nach
(bisher nur Kontrolle abgedeckt), inkl. Foto-Upload per Multipart und
eigenem Melde-Screen. Zusätzlich Backend-Threat-Model erstellt und daraus
DOC-006 (IDOR bei Dokument-Download) als Kachel im Backlog angelegt.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VDMvneg6wKfXdXvTYNd3dN
2026-09-12 21:42:44 +02:00

6.8 KiB
Raw Permalink Blame History

Threat Model: MABEA-Backend

Stand: 2026-09-12 Scope: gesamtes FastAPI-Backend (backend/app) — Auth/RBAC, Dokument-Upload/-Download, Systemeinstellungen, DB-Zugriff. Ausgeklammert: Frontend (React/PWA), Server-Härtung (nginx/systemd/Firewall — eigener Skill), Flutter-App.

Systemüberblick

Internet/Tablet (Client)
   │  HTTPS (nginx terminiert TLS)
   ▼
nginx (Rate-Limit /auth/login 5r/m, Reverse Proxy)
   │
   ▼
FastAPI-Backend (uvicorn, systemd)
   │  JWT Bearer (sub=login, exp, kein Rollen-Claim)
   ▼
Postgres (SQLAlchemy async)
   │
   ▼
Upload-Verzeichnis (lokales Filesystem, UUID-Dateinamen)
   │
   ▼
Sentry (extern, optional, DSN aus DB/ENV) — send_default_pii=False

Trust-Boundaries:

  1. Client ↔ nginx/Backend (nicht vertrauenswürdig — jeder Request potenziell manipuliert)
  2. Backend ↔ Postgres (vertrauenswürdig, aber SQL-Injection-relevant bei Rohkonstrukten)
  3. Backend ↔ lokales Filesystem (Uploads)
  4. Backend ↔ externes Sentry (Datenabfluss-Grenze)

Findings

# STRIDE Komponente Bedrohung Schweregrad Status Gegenmaßnahme
1 Spoofing auth.py Login Login-Enumeration per Antwortzeit (Timing-Angriff) Mittel abgedeckt verify_password_konstante_zeit + Dummy-Hash (security.py:11-27)
2 Spoofing JWT Schwacher/Default-Secret in Produktion Kritisch abgedeckt Startup-Guard in main.py:20-24 verweigert Boot bei Platzhalter-Secret
3 Elevation of Privilege deps.py Rollen Rollenänderung wirkt erst nach Token-Ablauf, falls Rollen im JWT gecacht wären Niedrig abgedeckt Bewusst keine Rollen im Token, get_current_user liest live aus DB (deps.py:30-38, security.py:31-32)
4 Elevation of Privilege dokument.py Download/Versionen IDOR: dokument_id per UUID direkt aus Pfad, jeder eingeloggte Nutzer könnte fremde Dokumente abrufen Hoch teilweise offen Nur Rollen-Check für eingeschränkte Dokumenttypen (_pruefe_eingeschraenkten_zugriff), aber kein Check, ob der Nutzer überhaupt Zugriff auf die zugehörige Entität (entitaet_typ/entitaet_id, z.B. Standort/Objekt) hat — jeder authentifizierte Nutzer mit dokument_id (UUID, aber z.B. aus geteiltem Link/Referer/Logs erratbar) kann GET /dokumente/{id}/download und /versionen aufrufen
5 Denial of Service Uploads (dokument.py) Kein Rate-Limit auf /dokumente POST — viele große Uploads (bis max_upload_size_mb) könnten Storage/CPU (SHA-256-Hashing) erschöpfen Mittel fehlt Rate-Limit analog auth/login (slowapi) auf Upload-Endpunkt erwägen
6 Information Disclosure dokument.py Upload-Antwort/Fehler Bei DateityperlaubtError/DateizugrossError — prüfen, ob Exception-Handler generische Meldung zurückgibt (nicht Stacktrace) Niedrig nicht geprüft (Exception-Handler außerhalb Scope dieser Analyse) Global Exception-Handler auf generische 4xx-Meldung ohne Interna prüfen
7 Tampering Uploads Whitelist MIME-Type serverseitig, aber mime_type kommt aus UploadFile.content_type (Client-gesteuert, fälschbar) — ein .html/.svg mit falschem Content-Type: application/pdf könnte durchrutschen Mittel fehlt Magic-Bytes-Prüfung (z.B. python-magic) zusätzlich zum Client-Content-Type, bevor Datei akzeptiert wird
8 Spoofing/CSRF Bearer-Token allgemein Kein CORS-Middleware im Code gefunden (grep CORSMiddleware leer) Mittel zu klären Falls Frontend von anderer Origin lädt (z.B. während Dev), fehlt CORS-Konfiguration → Requests würden von Browsern blockiert (eher Verfügbarkeits- als Sicherheitsproblem) oder, falls doch allow_origins=["*"] irgendwo gesetzt ist (nicht gefunden), wäre das ein Credential-Leak-Risiko. Mit reinem Bearer-Token (kein Cookie) ist CSRF strukturell kein Thema — sauber
9 Repudiation Kritische Aktionen (Dokument löschen, Rollen ändern, Materialbewegungen) Kein zentrales Audit-Log erkennbar auf Modell-Ebene — nur historie.py als eigenes Modell, dessen Abdeckung nicht verifiziert wurde Mittel teilweise (historie.py existiert) Prüfen, ob historie-Modell alle sicherheitsrelevanten Aktionen (Löschung, Rollenvergabe, Sentry-DSN-Änderung) tatsächlich mitschreibt
10 Denial of Service auth/login App-seitiges Rate-Limit (5/minute) nur per get_remote_address — hinter NAT/Firewall teilen sich viele Nutzer eine IP, ein böswilliger Nutzer kann andere aussperren (Fail2ban-ähnlicher Nebeneffekt) Niedrig bekannter Trade-off Dokumentiert als Verteidigung-in-der-Tiefe, primärer Schutz ist nginx — akzeptabel für Zielgruppe (Rettungsdienst-Standorte, kein Massenpublikum)
11 Information Disclosure /einstellungen/sentry GET Bewusst ohne Auth erreichbar Niedrig abgedeckt/bewusst Begründet: DSN kein Geheimnis, muss vor Login ladbar sein (Kommentar in Code) — korrekt eingeschätzt
12 Elevation of Privilege permission.py Custom-Rollen BenutzerRolleZuordnung PK ist (benutzer_id, rolle_id) — keine Mehrfachzuweisung derselben Rolle an verschiedenen Standorten möglich Niedrig bekannte Einschränkung (dokumentiert im Code) Kein Sicherheitsrisiko, nur funktionale Grenze — kein Handlungsbedarf aus Security-Sicht

Bereits abgedeckt

  • Timing-sicherer Login (security.py)
  • Startup-Guard gegen Default-JWT-Secret (main.py)
  • Whitelist-Ansatz bei Datei-Uploads (MIME-Type, Größe, serverseitig generierter Dateiname gegen Path-Traversal) (services/dokument.py)
  • Rollen/Berechtigungen frisch aus DB pro Request, kein Stale-Token-Problem
  • Granulares Berechtigungssystem zusätzlich zum festen 4-Rollen-System, inkl. Standort-Scoping (ZUST-003)
  • Rate-Limiting auf Login (defense in depth: nginx + slowapi)
  • Sentry ohne PII (send_default_pii=False), DSGVO-bewusst kommentiert
  • SHA-256-Duplikaterkennung bei Uploads

Offene Empfehlungen (priorisiert)

  1. Hoch — IDOR bei Dokument-Download/Versionen (dokument.py:113-126,175-186): Zugriff zusätzlich gegen Berechtigung auf die referenzierte Entität (entitaet_typ/entitaet_id, z.B. Standort des Objekts) prüfen, nicht nur gegen den Dokumenttyp.
  2. Mittel — Magic-Bytes-Validierung bei Upload ergänzen, nicht nur auf Client-Content-Type vertrauen.
  3. Mittel — Rate-Limit auf /dokumente POST (Upload) ergänzen.
  4. Mittel — Verifizieren, dass historie-Modell sicherheitsrelevante Aktionen (Löschen, Rollenvergabe, Sentry-DSN-Änderung) tatsächlich protokolliert.
  5. Niedrig — Prüfen, ob CORS explizit konfiguriert ist (aktuell keine CORSMiddleware gefunden) — falls Frontend je von separater Origin ausgeliefert wird, Konfiguration nachtragen statt improvisiert allow_origins=["*"] zu setzen.

Kein Ticket automatisch angelegt — bei Bedarf sag Bescheid, dann lege ich Kacheln zu Punkt 1–3 im Backlog an.