Security-Hardening (Pentest-Findings F-02 bis F-07)
- CSRF-Schutz: session-gebundenes Token in allen POST-Formularen, serverseitig per before_request geprueft; /nic/update ausgenommen (Basic-Auth-API) - Brute-Force-Schutz: DB-gestuetzter Login-Lockout pro Client-IP (5 Fehlversuche -> 15 min), echte IP via ProxyFix/X-Forwarded-For - SSRF: validate_plesk_url() erzwingt http(s) und blockt Link-Local/Metadata, Multicast und reservierte Ziele - Session-Cookies: HttpOnly, SameSite=Lax, Secure (per Env abschaltbar) - Security-Header: CSP, X-Frame-Options, X-Content-Type-Options, Referrer-Policy - Generische Plesk-Fehlermeldungen (keine internen URLs im UI) - CSS/JS nach static/ ausgelagert -> strikte CSP ohne 'unsafe-inline' - login_attempts-Tabelle + README-Security-Abschnitt Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -34,10 +34,14 @@ sie samt Tabellen erzeugt; ein Default-Admin (`admin` / `admin`) wird angelegt.
|
||||
|
||||
### Umgebungsvariablen (`docker-compose.yml`)
|
||||
|
||||
| Variable | Bedeutung | Default |
|
||||
|--------------|------------------------------------------------------|----------------------|
|
||||
| `DB_PATH` | Pfad zur SQLite-Datei **im Container** | `/data/dyndns.db` |
|
||||
| `SECRET_KEY` | Flask-Session-Key — **unbedingt ändern** (`openssl rand -hex 32`) | zufällig pro Start |
|
||||
| Variable | Bedeutung | Default |
|
||||
|-------------------------|------------------------------------------------------|----------------------|
|
||||
| `DB_PATH` | Pfad zur SQLite-Datei **im Container** | `/data/dyndns.db` |
|
||||
| `SECRET_KEY` | Flask-Session-Key — **unbedingt ändern** (`openssl rand -hex 32`) | zufällig pro Start |
|
||||
| `SESSION_COOKIE_SECURE` | Session-Cookie nur über HTTPS senden. Für lokale http-Tests auf `0` setzen. | `1` |
|
||||
|
||||
> **`SECRET_KEY` unbedingt setzen:** Bleibt er auf dem Default, wird bei jedem
|
||||
> Neustart ein zufälliger Key erzeugt und alle Sessions werden ungültig.
|
||||
|
||||
> **Wichtig:** Das Volume mountet ein **Verzeichnis** (`./data:/data`), nicht die
|
||||
> Datei direkt. Würde man die noch nicht existierende Datei mounten
|
||||
@@ -149,6 +153,27 @@ nginx-Direktiven einbinden.
|
||||
|
||||
---
|
||||
|
||||
## Sicherheit
|
||||
|
||||
- **CSRF-Schutz:** Alle ändernden POST-Formulare tragen ein Session-gebundenes
|
||||
Token (`csrf_token`), das serverseitig per `before_request` geprüft wird. Der
|
||||
Router-Endpoint `/nic/update` ist ausgenommen (reine API mit Basic-Auth).
|
||||
- **Brute-Force-Schutz:** Nach `LOGIN_MAX_FAILS` (5) Fehlversuchen pro Client-IP
|
||||
wird der Login für `LOGIN_LOCK_MINUTES` (15) gesperrt. Die echte Client-IP wird
|
||||
über `X-Forwarded-For` (nginx, via `ProxyFix`) ermittelt.
|
||||
- **Session-Cookies:** `HttpOnly`, `SameSite=Lax` und (per Default) `Secure`.
|
||||
- **Security-Header:** `Content-Security-Policy`, `X-Frame-Options: DENY`,
|
||||
`X-Content-Type-Options: nosniff`, `Referrer-Policy: no-referrer`.
|
||||
- **SSRF-Schutz:** Die admin-konfigurierte Plesk-URL muss `http(s)` sein; Ziele
|
||||
in Link-Local-/Cloud-Metadata- (`169.254.0.0/16`), Multicast- und reservierten
|
||||
Bereichen werden abgelehnt. Private-/Loopback-Adressen bleiben erlaubt, da
|
||||
Plesk häufig intern oder am selben Host läuft.
|
||||
- **Generische Fehlermeldungen:** Verbindungsfehler zu Plesk leaken keine
|
||||
internen URLs mehr; Details landen nur im Server-Log.
|
||||
|
||||
> **Erste Maßnahme nach dem Setup:** Das Default-Login `admin` / `admin` unter
|
||||
> *Einstellungen → Admin-Passwort ändern* sofort ersetzen.
|
||||
|
||||
## Architektur
|
||||
|
||||
```
|
||||
@@ -157,7 +182,8 @@ app/
|
||||
├── database.py SQLite-Schema, Migration, Settings-Helfer
|
||||
├── plesk.py Plesk-REST-API: Verbindungstest + A-Record anlegen/aktualisieren
|
||||
├── wsgi.py gunicorn-Einstieg (ruft init_db beim Start)
|
||||
└── templates/ Bootstrap-Oberfläche
|
||||
├── templates/ Bootstrap-Oberfläche
|
||||
└── static/ ausgelagertes CSS/JS (ermöglicht strikte CSP ohne 'unsafe-inline')
|
||||
```
|
||||
|
||||
### Datenmodell
|
||||
|
||||
Reference in New Issue
Block a user