Erlang/OTP SSH Vulnerable to Pre-Authentication RCE
Corrija agora. Ela está sob exploração confirmada pelo CISA e tem exploit funcional público.
Apply mitigations per vendor instructions, follow applicable BOD 22-01 guidance for cloud services, or discontinue use of the product if mitigations are unavailable.
Resumo
Falha de CWE-306 (ausência de verificação de autenticação) no servidor SSH da biblioteca `ssh` do Erlang/OTP: a máquina de estados aceitava mensagens do protocolo de conexão SSH (channel open, channel request) antes de a etapa de autenticação terminar. Um atacante sem credenciais podia abrir um canal e enviar um `channel_request` do tipo "exec" pré-autenticação, que o servidor processava como se a sessão já estivesse autenticada — resultando em execução de código arbitrário na VM Erlang. Afeta qualquer aplicação que rode `ssh:daemon` (papel de servidor) da própria biblioteca `ssh` do OTP, o que inclui produtos embarcados e appliances (Cisco, NetApp, etc.) que usam essa stack para gerenciamento remoto.
Detalhamento técnico
A implementação da RFC 4252 exige que mensagens SSH com número >= 80 (mensagens do protocolo de conexão, RFC 4254, como SSH_MSG_CHANNEL_OPEN e SSH_MSG_CHANNEL_REQUEST) só sejam aceitas depois que a autenticação (SSH_MSG_USERAUTH_SUCCESS) for concluída. No módulo `ssh_connection.erl`, a função `handle_msg/4` despachava essas mensagens para o tratamento normal de canal independentemente do estado `#ssh{authenticated}`. Não havia nenhuma cláusula que verificasse `authenticated == false` antes de processar o payload — a checagem simplesmente não existia no fluxo de mensagens do lado servidor.
O patch (visível nos três commits de backport para os ramos 25/26/27) adiciona uma cláusula de guarda: quando o processo está no papel `server` e `Ssh#ssh.authenticated =:= false`, qualquer mensagem que não seja `ssh_msg_disconnect` gera um log de depuração e força a desconexão com `SSH_DISCONNECT_PROTOCOL_ERROR`. Ou seja, a correção não é um novo parser ou sanitização de input — é a adição da verificação de estado que faltava.
O vetor de exploração demonstrado no próprio caso de teste (`early_rce`) manda um `channel_open` seguido de um `channel_request` do tipo "exec" cujo payload é uma string Erlang (`lists:seq(1,10).` no teste, mas qualquer expressão válida na prática, incluindo chamadas como `os:cmd/1`). O manipulador padrão de "exec" da biblioteca avalia essa string como código Erlang via `erl_eval`, então o atacante controla diretamente a expressão executada dentro do processo BEAM do servidor — com os privilégios do processo Erlang, não de um shell do sistema, mas suficiente para chamar funções que executam comandos do SO.
O atacante controla: o payload textual do `channel_request` (a expressão Erlang a ser avaliada) e o timing do ataque (enviar antes de qualquer troca de autenticação). Não precisa de usuário, senha, chave pública nem de nenhuma configuração incomum do servidor SSH — o bug está na máquina de estados do protocolo, não em uma feature opcional.
Como é explorada
Pré-requisito real é único e simples: acesso de rede TCP até uma porta onde um processo Erlang/OTP esteja rodando `ssh:daemon` (papel servidor). Não é necessário conhecer usuário ou senha, nem burlar autenticação por chave — o atacante nunca chega a enviar `SSH_MSG_USERAUTH_REQUEST`. Basta completar o handshake de transporte (hello, kexinit, key exchange) e, assim que o canal de criptografia está ativo, enviar `SSH_MSG_CHANNEL_OPEN` seguido de `SSH_MSG_CHANNEL_REQUEST` tipo "exec" com a expressão desejada como payload.
A complexidade é baixa (AC:L no CVSS): não há necessidade de manipular parâmetros de kex, nem contornar proteções adicionais — a única condição é que o servidor use o manipulador padrão de "exec" da lib (a mesma que avalia expressões Erlang), que é o comportamento de fábrica quando a aplicação não substitui o callback de shell/exec. Isso está confirmado por PoC pública (script Python que monta manualmente os pacotes SSH necessários) e por módulo Metasploit já disponível, o que reduz a barreira de exploração a praticamente zero. A CISA já confirma exploração ativa em produção (entrada no catálogo KEV).
O resultado final é execução de código dentro do runtime Erlang do servidor, com potencial de leitura/gravação de arquivos, pivotamento na rede e comprometimento total do host dependendo dos privilégios do processo BEAM — o CVSS 10.0 reflete justamente essa combinação de vetor de rede, sem autenticação, sem interação do usuário e impacto total de confidencialidade/integridade/disponibilidade.
Versões
Como se proteger
A correção definitiva é atualizar para OTP-27.3.3, OTP-26.2.5.11 ou OTP-25.3.2.20 (aplicação `ssh` nas versões 5.2.10, 5.1.4.8 ou 4.15.3.12, respectivamente) — qualquer versão igual ou superior à correspondente ao seu ramo já contém a checagem de estado de autenticação que faltava. O advisory do fornecedor also afirma que versões anteriores a OTP 17.0 provavelmente também são afetadas, mas não há comparação de versão bem definida para esse período (esquema de versionamento anterior).
Se a atualização não é viável imediatamente, o único paliativo real recomendado pelo fornecedor é desligar o servidor SSH da aplicação Erlang/OTP (não usar `ssh:daemon`) ou bloquear acesso à porta via firewall/controle de rede, restringindo a origem a hosts confiáveis. Isso não é um WAF ou regra de assinatura — a falha está no processamento de protocolo antes da autenticação, então qualquer conexão TCP alcançando a porta é suficiente para o ataque; segmentação de rede é o controle compensatório efetivo.
O que não funciona: trocar chaves de host, desabilitar métodos de autenticação específicos (senha vs chave pública) ou reforçar políticas de autenticação não mitiga nada, porque o ataque nunca chega à fase de autenticação — ele explora justamente a ausência da checagem antes dela. Restringir os tipos de canal/exec permitidos na configuração do daemon também não é confirmado como mitigação eficaz, já que o bug está na aceitação da mensagem antes da fase de auth, não no conteúdo do payload em si.
Como detectar
O tráfego SSH é criptografado após a troca de chaves, então não é possível inspecionar o conteúdo do `channel_request` "exec" via captura de pacotes de rede sem decriptação — a assinatura de exploração não é visível em NIDS tradicional além de metadados de timing (canal aberto e requisição de exec logo após o key exchange, sem troca de `SSH_MSG_USERAUTH_*` completa). Em ambientes já corrigidos (pós-patch), o próprio código de defesa gera um log de depuração com a mensagem "Connection terminated. Unexpected message for unauthenticated user" via `?LOG_DEBUG`, seguido de desconexão com código de erro de protocolo — presença recorrente desse log é forte indício de tentativas de exploração contra uma instância já atualizada.
Em instâncias não corrigidas não há sinal de aplicação equivalente, porque o servidor simplesmente processa o comando como legítimo; o único rastro provável é comportamental — processos ou comandos inesperados originados do processo BEAM/erlang, conexões SSH cuja sessão nunca completou autenticação nos logs de auditoria do sistema, ou uso do PoC/módulo Metasploit público (que segue o padrão hello → kexinit → channel_open → channel_request exec) capturável em honeypots ou telemetria de fluxo caso a decriptação esteja disponível.