165 lines
10 KiB
Markdown
165 lines
10 KiB
Markdown
# Sicherheitstest-Bericht: Jumphost Gateway
|
||
|
||
**Datum:** 19.08.2026 | **Version des Testobjekts:** Implementierung v0.2 | **Tester:** Claude (im Auftrag von Midas)
|
||
|
||
## 1. Scope und Methodik
|
||
|
||
### 1.1 Was tatsächlich getestet wurde
|
||
|
||
Diese Sitzung lief in einer isolierten Cloud-Sandbox ohne Zugriff auf ein
|
||
reales Netzwerk, echte SSH-/RDP-Zielsysteme oder eine laufende `guacd`-
|
||
Instanz. Der Test ist daher ein **Anwendungssicherheitstest (SAST + gezielter
|
||
DAST) gegen den Quellcode und die lokal laufende Anwendung**, kein
|
||
vollständiger Infrastruktur-/Netzwerk-Penetrationstest. Konkret durchgeführt:
|
||
|
||
1. **Statische Analyse (SAST):** `bandit` gegen den gesamten Python-Code
|
||
(`app/`, `scripts/`), `pip-audit` gegen alle Produktiv- und
|
||
Dev-Abhängigkeiten.
|
||
2. **Dynamischer Sicherheitstest (DAST, White-/Grey-Box):** 17 gezielte
|
||
Angriffstests (`tests/test_pentest_security.py`) gegen die echte
|
||
FastAPI-Anwendung inkl. SQLite-Datenbank, ausgeführt über den
|
||
ASGI-Transport (kein echtes TCP/TLS, aber derselbe Anwendungscode wie in
|
||
Produktion) sowie ergänzend manuelle Verifikation per `curl` gegen einen
|
||
laufenden `uvicorn`-Prozess.
|
||
3. **Manuelle Code-Review-Punkte:** Session-/Cookie-Handling, RBAC-
|
||
Durchsetzung, Key-Handling, Audit-Log-Integrität — im Rahmen der unter 2)
|
||
genannten Testfälle verifiziert, nicht nur gelesen.
|
||
|
||
### 1.2 Was NICHT getestet wurde (out of scope in dieser Umgebung)
|
||
|
||
| Bereich | Warum nicht möglich |
|
||
|---|---|
|
||
| Netzwerk-/Infrastruktur-Pentest (Portscans, TLS-Konfiguration des realen nginx/nftables-Setups, Firewall-Umgehung) | Kein reales Zielsystem/Netzwerk in dieser Sandbox verfügbar |
|
||
| RDP/`guacd`-Pfad end-to-end (echtes Windows-Ziel, echtes `guacd`+FreeRDP) | Keine RDP-Zielumgebung verfügbar; Protokoll-Handshake wurde nur gegen die Spezifikation implementiert, nicht live gegenverifiziert |
|
||
| SSH-Proxy end-to-end gegen ein echtes Linux-Ziel | Kein SSH-Zielsystem verfügbar; SSH-Verbindungslogik ist durch `asyncssh` (etabliert, nicht selbst geschrieben) abgedeckt, die serverseitige Anbindung (Key-Laden, Host-Key-Pinning) wurde isoliert/unit-artig, nicht end-to-end getestet |
|
||
| Ansible-Rollen-Ausführung gegen ein reales Debian/Ubuntu-System | Kein Root-Zielsystem verfügbar; nur `--syntax-check` + YAML/Jinja-Parsing + Variablen-/Handler-Konsistenzprüfung möglich |
|
||
| OpenSCAP/CIS-Benchmark-Tool-Lauf | Kein Zielsystem, auf dem `oscap` laufen könnte |
|
||
| Social Engineering, physische Sicherheit | Nicht anwendbar auf ein Code-Review |
|
||
| Lasttest / Denial-of-Service-Robustheit | Nicht Teil dieses Auftrags; separat zu betrachten |
|
||
|
||
**Konsequenz:** Die hier bestätigte Sicherheit bezieht sich auf die
|
||
Anwendungslogik selbst. Vor Produktivbetrieb sind ein echter
|
||
Netzwerk-Pentest gegen die deployte Instanz und ein Live-Test des
|
||
RDP-Pfads weiterhin erforderlich (siehe Abschnitt 4).
|
||
|
||
---
|
||
|
||
## 2. Ergebnisse: Statische Analyse
|
||
|
||
### 2.1 bandit (Python-Sicherheitslinter)
|
||
|
||
| Vorher | Nachher |
|
||
|---|---|
|
||
| 1× Medium (SQL-String-Konstruktion, B608) | 0 |
|
||
| 6× Low (breite `except`-Blöcke B110, subprocess-Nutzung B404/B603) | 0 (5× behoben durch Logging statt `pass`, 2× als geprüft/gerechtfertigt mit `# nosec` + Begründungskommentar markiert) |
|
||
|
||
Alle Findings wurden behoben oder mit expliziter, im Code dokumentierter
|
||
Begründung als bewusste Entscheidung markiert (kein stilles Wegklicken).
|
||
Details: `app/auth/routes.py` (SQL-Konstruktion durch zwei feste
|
||
parametrisierte Statements ersetzt), `app/security/av_scan.py`,
|
||
`app/rdp_proxy/guacd_client.py`, `app/rdp_proxy/ws_tunnel.py`,
|
||
`app/ssh_proxy/terminal_ws.py`.
|
||
|
||
### 2.2 pip-audit (Abhängigkeits-Schwachstellen)
|
||
|
||
**16 bekannte Schwachstellen in 2 Paketen gefunden** (Stand vor Bereinigung):
|
||
|
||
| Paket | Version (vorher) | Version (nachher) | Bekannte CVEs/Advisories |
|
||
|---|---|---|---|
|
||
| `cryptography` | 43.0.3 | **50.0.0** | 6 (u.a. PYSEC-2026-3553/3554, GHSA-537c-gmf6-5ccf) |
|
||
| `starlette` | 0.46.2 | **1.6.0** | 10 (u.a. PYSEC-2026-1941/1942/2280/2281) |
|
||
| `fastapi` | 0.115.x | **0.141.1** | (transitiv mit starlette aktualisiert) |
|
||
| `pytest`, `pytest-asyncio` (Dev-only) | 8.3.5 / 0.24.0 | **9.1.1 / 1.4.0** | PYSEC-2026-1845 |
|
||
|
||
Nach dem Upgrade: **0 bekannte Schwachstellen** in einem vollständigen
|
||
`pip-audit`-Lauf über die gesamte aufgelöste Umgebung. `requirements.txt`
|
||
und `requirements-dev.txt` sind jetzt exakt (lockfile-artig) auf die
|
||
geprüften Versionen gepinnt.
|
||
|
||
⚠️ **Wichtiger Fund durch die Regressionstests:** Das Upgrade von Starlette
|
||
0.46 → 1.6 hat die Aufrufkonvention von `Jinja2Templates.TemplateResponse()`
|
||
geändert (alte Signatur `TemplateResponse(name, {"request": request})` wurde
|
||
entfernt, neue Signatur ist `TemplateResponse(request, name, context)`). Ohne
|
||
die Pentest-Testsuite (`test_security_headers_present_on_every_response` u.a.,
|
||
die alle Seiten inkl. `/` aufrufen) wäre dieser **funktionale Regressionsbug**
|
||
erst im Betrieb aufgefallen: `GET /`, `/dashboard`, `/terminal/{id}`,
|
||
`/rdp/{id}` hätten alle mit HTTP 500 geantwortet. Behoben in `app/main.py`;
|
||
alle vier Routen sind erneut per Test und manuellem `curl`-Aufruf verifiziert
|
||
(Status 200, korrektes HTML).
|
||
|
||
---
|
||
|
||
## 3. Ergebnisse: Dynamischer Sicherheitstest (17 Testfälle)
|
||
|
||
Alle 17 Testfälle sind **grün** (siehe `tests/test_pentest_security.py`,
|
||
ausführbar mit `pytest tests/test_pentest_security.py -v`). Zusammenfassung
|
||
nach Kategorie:
|
||
|
||
| Kategorie | Geprüft | Ergebnis |
|
||
|---|---|---|
|
||
| **Authentifizierung** | Keine Username-Enumeration über Fehlermeldungen; Account-Lockout nach 5 Fehlversuchen (auch mit korrektem Passwort danach gesperrt); IP-basiertes Rate-Limiting greift bei >10 Versuchen/Minute; klassische SQLi-Payloads im Username-Feld führen weder zu 500 noch zu Auth-Bypass, Datenbank bleibt intakt | ✅ Bestanden |
|
||
| **Session-/Cookie-Sicherheit** | Cookie trägt `HttpOnly`, `Secure`, `SameSite=Strict`; ein mit falschem Secret gefälschtes Cookie wird abgelehnt; Passwortänderung invalidiert alte Sessions (`session_version`-Mechanismus); "Überall abmelden" invalidiert das Cookie sofort | ✅ Bestanden |
|
||
| **RBAC / IDOR** | Nicht-Admin kommt an keinen Admin-Endpunkt (weder lesend noch schreibend); Nutzer mit Rolle nur auf Hostgruppe A wird bei Host aus Hostgruppe B mit 403 blockiert — unabhängig davon, dass die Host-ID gültig/erratbar ist; eine Rolle für einen Aktionstyp (z.B. `ssh_connect`) gewährt **nicht automatisch** eine andere (z.B. `file_transfer`) auf demselben Host | ✅ Bestanden |
|
||
| **Dateitransfer-Härtung** | Uploads über dem konfigurierten Limit werden mit 413 abgelehnt; als "infected" markierte Uploads werden blockiert (400) und erzeugen **keinen** Eintrag in `file_transfers` (kein falsches Erfolgssignal) | ✅ Bestanden |
|
||
| **HTTP-Security-Header** | CSP, `X-Frame-Options: DENY`, `X-Content-Type-Options: nosniff`, `Strict-Transport-Security`, `Referrer-Policy` auf **jeder** Antwort inkl. Fehlerantworten; keine Stacktraces/Dateipfade in Fehlermeldungen | ✅ Bestanden |
|
||
| **Audit-Log-Vollständigkeit** | Fehlgeschlagene und erfolgreiche Logins sowie TOTP-Enrollment erzeugen Audit-Einträge; Hash-Chain bleibt nach einem vollständigen Testlauf (viele parallele Nutzer, Fehlversuche, Admin-Aktionen) durchgängig intakt | ✅ Bestanden |
|
||
|
||
### 3.1 Bewertung nicht-automatisierter Beobachtungen (kein Fix nötig, dokumentiert)
|
||
|
||
- **Pfadangabe bei SFTP-Dateitransfer** (`remote_path`-Parameter): erlaubt
|
||
beliebige Pfade wie `../../etc/passwd`. Das ist **kein Jumphost-seitiges
|
||
Path-Traversal**, da dieser Pfad ausschließlich an das **Zielsystem** per
|
||
SFTP übergeben wird (keine lokale Dateisystem-Interaktion auf dem Jumphost
|
||
selbst) — die Zugriffskontrolle liegt beim Zielsystem-Betriebssystem über
|
||
den `ssh_username`. Funktional gewollt (Nutzer soll das Zielsystem wie mit
|
||
einem SFTP-Client durchsuchen können). Empfehlung für sehr sensible
|
||
Hostgruppen: zusätzliche pfadbasierte Allow-Lists auf Anwendungsebene als
|
||
optionale Erweiterung (Konzept Kap. 12 könnte hierzu ergänzt werden).
|
||
- **`hosts.address` ist nur admin-editierbar**, nicht durch normale Nutzer
|
||
beeinflussbar — ein SSRF-artiger Angriffsvektor über beliebige Zieladressen
|
||
ist daher kein nutzerseitig ausnutzbares Risiko, sondern liegt in der
|
||
bestehenden Admin-Vertrauensgrenze (Admins könnten ohnehin beliebige
|
||
RBAC-Rechte vergeben).
|
||
|
||
---
|
||
|
||
## 4. Offene Punkte vor Produktivbetrieb (Restrisiko)
|
||
|
||
Diese Punkte waren bereits im ursprünglichen README als offen vermerkt und
|
||
bleiben es, da sie eine echte Zielumgebung erfordern, die in dieser Sandbox
|
||
nicht existiert:
|
||
|
||
1. **Netzwerk-Pentest gegen die tatsächlich deployte Instanz** (TLS-Konfiguration
|
||
von nginx/direktem Uvicorn-TLS, Firewall-Regeln, tatsächliches
|
||
Verhalten von fail2ban) — Black-Box-Test von außen.
|
||
2. **RDP-Pfad live gegen `guacd` + FreeRDP + Windows-Ziel testen** —
|
||
insbesondere Parameter-Namen/-Reihenfolge der `connect`-Instruktion gegen
|
||
die konkret eingesetzte guacd-Version verifizieren (siehe
|
||
`app/rdp_proxy/guacd_client.py`, Docstring-Hinweis).
|
||
3. **`ansible-playbook --check` bzw. echter Rollout in einer
|
||
Staging-Umgebung** — insbesondere die `pam_faillock`-Einbindung und die
|
||
`/tmp`-/`/dev/shm`-Remount-Logik sind distributionsversionsabhängig und
|
||
sollten vor der Produktivfreigabe an einem echten System bestätigt werden
|
||
(siehe `ansible/roles/os_hardening/CIS_STIG_MAPPING.md`, Abschnitt
|
||
„Bekannte Einschränkungen").
|
||
4. **OpenSCAP-Lauf** gegen das gewählte CIS/STIG-Profil zur unabhängigen
|
||
Bestätigung der Hardening-Rolle.
|
||
5. Alle bereits zuvor im README genannten funktionalen Erweiterungen
|
||
(LDAP/AD, WebAuthn, verteiltes Rate-Limiting, CSRF-Token als zusätzliche
|
||
Schicht) sind weiterhin nicht umgesetzt.
|
||
|
||
## 5. Fazit
|
||
|
||
Die Anwendungslogik hat den durchgeführten Sicherheitstest ohne verbleibende
|
||
offene Findings bestanden: keine Auth-/Session-Bypässe, keine RBAC-/IDOR-
|
||
Lücken, keine SQL-Injection, vollständige Audit-Protokollierung,
|
||
durchgängige Security-Header, saubere Abhängigkeiten (0 bekannte CVEs). Der
|
||
Testlauf hat zusätzlich einen realen, durch das Dependency-Upgrade
|
||
eingeführten Funktionsfehler gefunden und behoben — ein gutes Beispiel dafür,
|
||
warum Sicherheitsupdates immer mit Regressionstests kombiniert werden
|
||
sollten, nicht isoliert eingespielt werden dürfen.
|
||
|
||
Die verbleibenden offenen Punkte sind ausschließlich solche, die eine echte
|
||
Zielinfrastruktur voraussetzen und in einer isolierten Code-Sandbox
|
||
grundsätzlich nicht abschließend geprüft werden können.
|