second commit
This commit is contained in:
164
Pentest_Report.md
Normal file
164
Pentest_Report.md
Normal file
@ -0,0 +1,164 @@
|
||||
# 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.
|
||||
Reference in New Issue
Block a user