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
6.8 KiB
6.8 KiB
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:
- Client ↔ nginx/Backend (nicht vertrauenswürdig — jeder Request potenziell manipuliert)
- Backend ↔ Postgres (vertrauenswürdig, aber SQL-Injection-relevant bei Rohkonstrukten)
- Backend ↔ lokales Filesystem (Uploads)
- 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)
- 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. - Mittel — Magic-Bytes-Validierung bei Upload ergänzen, nicht nur auf Client-
Content-Typevertrauen. - Mittel — Rate-Limit auf
/dokumentePOST (Upload) ergänzen. - Mittel — Verifizieren, dass
historie-Modell sicherheitsrelevante Aktionen (Löschen, Rollenvergabe, Sentry-DSN-Änderung) tatsächlich protokolliert. - Niedrig — Prüfen, ob CORS explizit konfiguriert ist (aktuell keine
CORSMiddlewaregefunden) — falls Frontend je von separater Origin ausgeliefert wird, Konfiguration nachtragen statt improvisiertallow_origins=["*"]zu setzen.
Kein Ticket automatisch angelegt — bei Bedarf sag Bescheid, dann lege ich Kacheln zu Punkt 1–3 im Backlog an.