Files
ssh-jumphost/Pentest_Report.md
2026-08-19 22:33:19 +02:00

10 KiB
Raw Blame History

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.