Como impedir MITM na primeira conexão SSH em qualquer VPS ou provedor de nuvem
(joachimschipper.nl)- ssh-init-vm injeta uma chave privada temporária de host SSH via cloud-init para impedir ataques man-in-the-middle na primeira conexão SSH a uma nova VM, confiando nela apenas enquanto gera ou obtém a chave de host de longo prazo
- Funciona até mesmo em VPS ou nuvens sem recursos dedicados de proteção de acesso, como a Hetzner Cloud, exigindo apenas o amplamente suportado cloud-init
- No modelo comum de Trust On First Use, ao digitar
yespara a pergunta dossh“The authenticity of host [...] can't be established”, um atacante pode fazer proxy do tráfego ou fornecer uma máquina que pareça ser a VM do usuário - Colocar diretamente a chave privada de host SSH de longo prazo no userdata do cloud-init ajuda a autenticar a primeira conexão, mas pode expor material sensível de chave por meio do serviço de metadados, SSRF, sistemas do provedor de nuvem ou a estação de trabalho do administrador
- O ssh-init-vm mantém a chave temporária em um diretório temporário e não a adiciona a
~/.ssh/known_hosts; em vez de salvar diretamente a saída da VM, registra a chave de longo prazo com base na rotação de chaves de host do OpenSSH
Problema de exposição do userdata do cloud-init
- Injetar a chave privada de host SSH de longo prazo com cloud-init permite colocar a chave pública em
~/.ssh/known_hostspara autenticar a primeira conexão, mas a chave privada pode vazar por vários caminhos - Um processo arbitrário dentro da VM normalmente pode obter o userdata a partir do serviço de metadados, que costuma ser legível; em VMs da Hetzner, o conteúdo do cloud-init pode aparecer em
http://169.254.169.254/hetzner/v1/userdata - Um atacante pode usar SSRF para fazer um processo vazar os metadados, e esse bloqueio nem sempre é aplicado mesmo em ambientes com proteção dedicada
- O userdata também pode ficar exposto em outros sistemas do provedor de nuvem; a Hetzner, por exemplo, afirma na documentação da API de criação de servidores para não armazenar “passwords or other sensitive information”
- A estação de trabalho do administrador também pode ser um local por onde o userdata do cloud-init passa ou fica armazenado, então inserir a chave privada de longo prazo cria risco de exposição durante todo o período em que ela for válida
Análise de segurança e modelo de ameaça
- A premissa é confiar no protocolo e na implementação do OpenSSH, sem depender da capacidade do administrador de detectar um ataque
-
Proteção contra atacantes de rede
- O que se busca proteger é a integridade da estação de trabalho do administrador e da VM
- O atacante é um intermediário que controla totalmente a rede e pode descobrir o userdata do cloud-init em algum momento depois que o script terminar, com sucesso ou falha
- A proteção funciona porque o atacante não conhece o material de chave enquanto ele ainda tem valor
- O script guarda a chave temporária de host SSH em um diretório temporário para evitar uso acidental e não adiciona a chave temporária a
~/.ssh/known_hosts
-
Quando a estação de trabalho do administrador está comprometida
- A proteção se limita à VM e à sua chave privada de host SSH de longo prazo
- Parte-se da hipótese de que o atacante controla totalmente a rede e a estação de trabalho do administrador, mas não acessa a VM real
- A chave privada de host SSH de longo prazo nunca esteve na estação de trabalho do administrador e, como o atacante não acessa a VM real, ele não obtém a chave de host de longo prazo da VM
- Se o atacante acessar a VM real, é bastante provável que consiga descobrir as chaves de host SSH com algo como
ssh root@<VM> cat /etc/ssh/ssh_host_*
-
Quando a VM ou o provedor está comprometido
- A proteção se limita à integridade da estação de trabalho do administrador
- O atacante pode controlar totalmente a rede e também a VM ou o provedor
- Mesmo nesse caso, a integridade da estação de trabalho do administrador é protegida pela premissa de que o OpenSSH é seguro
- Como defesa adicional, o script não grava diretamente a saída da VM em
~/.ssh/known_hosts; em vez disso, insere a chave de host SSH de longo prazo com base na rotação de chaves do OpenSSH - Essa abordagem impede que um host comprometido alimente o parser de
known_hostscom dados maliciosos e garante que apenas as chaves sob controle real da VM sejam registradas em~/.ssh/known_hosts - Ela também consegue lidar corretamente com opções do OpenSSH como
HashKnownHostse opções relacionadas no futuro
Condições para um ataque man-in-the-middle realmente funcionar
- O sucesso de um ataque man-in-the-middle depende de o usuário perceber de fato que todas as conexões estão indo desde o início para a máquina errada, de se recusar a digitar a senha e de configurar ou não o encaminhamento de agent ou X11 no
ssh - Segundo as condições simplificadas do ssh-mitm, as chances de sucesso são altas se o atacante conseguir enganar o usuário fornecendo acesso a uma máquina sob seu controle, em vez do host de destino real
- Também há sucesso se o atacante convencer o usuário a fornecer informações que permitam login no host real
- Se o usuário fizer login na máquina do atacante com senha, o atacante pode ter sucesso
- Se o usuário entrar com qualquer método de autenticação e depois digitar uma senha no prompt, o atacante pode ter sucesso
- Se o usuário entrar com qualquer método de autenticação e encaminhar a conexão do ssh-agent, o atacante pode ter sucesso
- Sem essas condições, o atacante precisaria de acesso ao host real para enganar o usuário, mas provavelmente falharia porque não conseguiria fazer login no host real apenas com a entrada fornecida pelo usuário
- Se o usuário encaminhar uma conexão X11, o atacante também pode conseguir atacar a estação de trabalho do administrador, independentemente do método de autenticação
1 comentários
Comentários no Lobste.rs
Muito legal poder automatizar isso. Pelo método manual, eu vinha confirmando a impressão digital SSH do servidor por um canal separado no console do provedor de nuvem
Como não administro tantas instâncias de nuvem, tudo bem ter algumas etapas manuais no provisionamento
Se você já automatizou a zona DNS, também dá para seguir outra abordagem: criar um token descartável com escopo bem limitado, permitindo, por exemplo, apenas criar um registro em
my-server-hostname.example.netPasse esse token para o servidor via
cloud-inite faça o servidor publicar sua chave SSH pública como registro SSHFP no DNS. Depois, o cliente SSH pode validar automaticamente o registro SSHFP, e a zona DNS precisa estar assinada com DNSSECEsse fluxo permite que o servidor mantenha sua chave de host SSH privada sem precisar fazer rotação de chave. A maioria dos provedores de DNS não oferece esse tipo de token descartável tão granular, mas você pode ter um serviço web interno simples que valide o token e então faça a chamada de API com um token permanente e sem limitação de escopo. O servidor SSH não consegue acessar esse token permanente
Ainda assim, eu provavelmente preferiria gerar certificados SSH em vez de gravar em um domínio com DNSSEC, e a partir daí vira uma questão de qual solução se encaixa melhor em cada ambiente
Fico curioso se você conhece algum software ou provedor que permita gerar tokens tão flexíveis assim, ou se é preciso desenvolver boa parte disso por conta própria
Bem elegante. Mas acho que no texto as datas de mês e dia foram invertidas
Há algum tempo eu cheguei a criar um serviço experimental para explorar esse mesmo problema do ovo e da galinha no SSH
Ele gera registros DNS SSHFP sob demanda; se tiver interesse, veja https://github.com/tedb/sshfp
Fico feliz de ver que isso lida com o serviço de metadados 169.254.169.254, que é relativamente consistente entre provedores de VM. Dá para confirmar olhando os vários itens
cloudinit/source/DataSource*.pynocódigo-fonte do cloud-initPessoalmente, venho ficando cada vez mais cansado do design e das limitações do
cloud-init. Tenho interesse em unificar a configuração de sistemas entre VMs locais no QEMU, máquinas remotas, contêineres e hardware físicoO projeto arch-boxes mostra como a imagem cloud-init do ArchLinux é criada, e trata-se de um conjunto bem simples de scripts shell. Adaptando essa abordagem com
guestfishou µvm, dá para usar exatamente os mesmos scripts para imagens compatíveis com OCI, imagens de disco para QEMU ou provedores de nuvem, e provisionamento de novas máquinas físicasCom algumas flags do QEMU, dá até para reproduzir a mesma abordagem sem depender de
cloud-init. Até onde eu sei, comsystemd.system-credentialsnão dá para passar chaves de host temporárias, apenas credenciais para~/.ssh/authorized_keys, comossh.authorized_keys.rootEm vez disso, você pode criar um arquivo unit para rodar na etapa do initrd ou junto com
systemd-firstboot.service. Esse unit pode ser incluído previamente na imagem ou injetado temporariamente com credenciaissystemd.extra-unit.*, e ativado com a opção de linha de comando do kernelsystemd.wants=…. No QEMU, dá para simular a presença de um serviço de metadados com-netdev user,id=metadata,net=169.254.0.0/16,dhcpstart=169.254.0.15,guestfwd=tcp:169.254.169.254:80-cmd:…. Só que provavelmente será preciso ativar a interface gerada, e isso também pode acabar sendo melhor resolvido com um unit temporárioCom isso, você obtém bastante flexibilidade com complexidade relativamente baixa ao fazer configuração de sistema consistente em vários tipos de “máquina”. Na prática, olhando só para esse problema, a melhor saída parece ser a ferramenta de criação de imagens gerar imagens de máquina com chaves de host fixas, e instalar como serviço do SystemD um script customizado de rotação de chave de host para executar no primeiro reboot ou no desligamento
HOOKsystemdem/etc/mkinitcpio.conf, pode — e na prática deve — escrever arquivos unit do SystemD para tarefas a serem executadas no initrdNa prática, usar isso dá só um pouquinho mais de trabalho do que escrever
{/etc,/usr/lib}/initcpio/hooksMas como é bem fácil habilitar
systemd-networkingesystemd-resolvedno initrd, o initrd pode assumir a responsabilidade pelo início do sistema e agendar tarefas antes de trocar para o sistema de arquivos raizClaro que, em hardware físico como notebooks, isso pode não se encaixar tão bem, já que a conexão Wi‑Fi pode exigir algo como
NetworkManager, mas funciona bem para VMs no QEMU e VMs hospedadas, e muitas tarefas de inicialização do sistema se encaixam naturalmente nesse espaçoO objetivo é não depender de
cloud-init, não ficar preso a um único provedor de nuvem, obter consistência entre máquinas físicas, contêineres, VMs locais e VMs hospedadas, e reduzir as dependências praticamente ao SystemD