4 pontos por GN⁺ 2024-06-24 | 1 comentários | Compartilhar no WhatsApp
  • sudo e doas dependem de binários setuid e de elevação de privilégios, então esta é uma experiência que delega a execução de comandos como root a um sshd local vinculado a um socket de domínio Unix
  • O objetivo é fazer com que apenas usuários autorizados executem comandos como root, sem deixar a capacidade de elevação de privilégios disponível para toda a sessão do usuário
  • A implementação consiste em um arquivo de chave SSH exclusivo para root, um /run/sshd/sshd.sock com acesso restrito e uma instância separada de sshd com as opções AuthorizedKeysFile e PermitRootLogin=yes
  • Como o ssh não tem uma opção para receber diretamente um socket já existente, no início foram usados ProxyCommand e socat; depois, ProxyUseFdpass e um pequeno script em Python passaram o descritor de arquivo do socket
  • Essa abordagem funciona, e o tratamento de segurança depende principalmente do OpenSSH, mas para uso diário é mais adequado transformar o passfd.py em um pequeno executável e envolver todo o comando ssh em um wrapper

Restrições estruturais do sudo e do doas

  • sudo e doas dependem de binários setuid e de elevação de privilégios para executar comandos como root
  • Esse desenho tem algumas limitações
    • Toda a sessão do usuário precisa manter a capacidade de realizar elevação de privilégios
    • Não funciona ao executar toda a sessão do usuário dentro de um namespace de usuário restrito
    • Binários setuid impõem restrições à configuração de segurança do sistema como um todo
  • s6-sudod é uma alternativa que divide o programa entre um servidor com privilégios e um cliente sem privilégios

Objetivo do experimento

  • Usar ssh localmente para cumprir o papel do sudo, mas sem expor essa instância de sshd à rede
  • Há duas condições centrais
    • Somente usuários autorizados podem executar comandos como root
    • Não usar elevação de privilégios

Configuração do sshd para login local como root

  • É criada uma chave SSH dedicada apenas para autenticação de root, armazenada em /root/.ssh/local_keys em vez do authorized_keys comum
  • Uma instância separada de sshd é vinculada a um socket de domínio Unix, e as permissões de /run/sshd/ são restringidas para impedir que usuários não autorizados acessem o socket
  • O exemplo de execução cria /run/sshd/sshd.sock com s6-ipcserver e passa ao sshd as seguintes opções
    • AuthorizedKeysFile=/root/.ssh/local_keys
    • PermitRootLogin=yes
  • O /etc/ssh/sshd_config não é alterado
    • Porque não se quer permitir login de root no sshd já existente, vinculado à rede
    • Na configuração atual, PermitRootLogin no é mantido

Bloqueio da conta root e tratamento do login por senha

  • A conta root estava bloqueada para impedir login por qualquer meio, usando um ! antes do hash da senha
  • O sshd interpreta esse prefixo ! como conta bloqueada e não permite login de root
  • O valor da senha de root em /etc/passwd é alterado, trocando ! por *
    • O sshd não interpreta * como um valor especial de bloqueio
    • Como * não corresponde a nenhum hash de senha, o login por senha continua efetivamente desativado
  • Além disso, PasswordAuthentication no está definido no sshd_config, e é mais seguro desabilitar a autenticação por senha no sshd

Conectando ao socket Unix com ssh

  • O sshd tem a flag -i para receber um socket já existente, mas o ssh não possui uma flag equivalente
  • No início, usou-se socat com ProxyCommand para conectar a /run/sshd/sshd.sock
  • O comando de conexão era composto pelos seguintes elementos
    • ProxyCommand='socat STDIO UNIX-CONNECT:/run/sshd/sshd.sock'
    • A chave root dedicada é especificada com -i .ssh/root-key.pub
    • Conexão para root@root
    • Mudança para o diretório atual e, em seguida, execução de um shell de login
  • A chave SSH usada era uma chave vinculada a hardware, então era preciso tocar fisicamente o dispositivo para aprovar a conexão
  • Um ssh-agent que só expõe a chave após aprovação explícita, como o hissh-agent, também pode ser uma alternativa

Overhead do socat e ProxyUseFdpass

  • O socat lê toda a entrada do ssh e só então escreve no socket, então o overhead da conexão acaba sendo praticamente duplicado
  • ProxyUseFdpass faz com que o comando envie o descritor de arquivo do socket ao ssh por stdout, e então o ssh se conecta usando esse socket
  • Com base em um exemplo de uso do OpenSSH ProxyUseFdpass de 2016, foi escrito o script em Python passfd.py
    • Ele se conecta ao socket de domínio Unix /run/sshd/sshd.sock
    • Usa sendmsg e SCM_RIGHTS para passar o descritor de arquivo
  • Depois disso, o comando de conexão passou a usar passfd.py em ProxyCommand e adicionar ProxyUseFdpass=yes
  • nc -FU /run/sshd/sshd.sock também parece possível, mas o manual diz explicitamente que -F não pode ser usado junto com -U

Conclusão e forma de uso no dia a dia

  • Essa técnica funciona, e o tratamento sensível de segurança fica principalmente a cargo do OpenSSH
  • O OpenSSH tem um bom histórico e permite usar vários métodos de autenticação, inclusive chaves SSH baseadas em hardware
  • O processo de configuração em um novo host não envolve etapas complexas, e o comando ipcserver pode ser executado por meio do gerenciador de serviços do sistema
  • O passfd.py é mais um hack rápido para viabilizar o experimento
  • Para uso diário, é mais adequado criar um pequeno executável com a mesma função, colocá-lo em /usr/local/bin e também envolver todo o comando ssh em um pequeno wrapper

1 comentários

 
GN⁺ 2024-06-24
Opiniões do Hacker News
  • O principal motivo para ser contra essa abordagem é o aumento da complexidade. Em vez de um único binário suid que lê um arquivo de configuração e chama exec(), passa a haver um binário rodando como root e escutando em um socket UNIX, e outro binário que fala com esse socket; ambos ainda precisam lidar com criptografia assimétrica
    Se a crítica central ao sudo/doas é “há um binário suid acessível por todos os usuários e, se houver um bug, ele pode ser explorado para escalonamento de privilégios”, basta fazer algo como abaixo
    chgrp wheel /usr/bin/sudo
    chmod o-rwx /usr/bin/sudo
    Ao colocar usuários do sudo no grupo wheel, apenas usuários que podem usar sudo conseguem ler os bytes do arquivo no disco e também executá-lo. Em termos de controle de acesso, a segurança é quase a mesma que na abordagem com sshd, em que só usuários wheel podem acessar o socket UNIX, mas a complexidade é muito menor
    Além disso, a abordagem com sshd não consegue limitar o acesso root a comandos específicos como o sudo faz; portanto, mesmo que exista um bug que contorne as restrições de comando do sudo, isso não concederia mais privilégios do que a abordagem com sshd
    Se a preocupação é que o gerenciador de pacotes estrague as permissões de /usr/bin/sudo, dá para corrigi-las periodicamente com cron, ou remover o sudo completamente e instalá-lo manualmente a partir do código-fonte em outro local. Claro que, nesse caso, a manutenção e as atualizações também ficam por sua conta

    • Pessoalmente, uso etckeeper para rastrear todas as mudanças em /etc. Mudanças feitas por instalação/atualização de software ou por pessoas ficam registradas, e ao atualizar para uma nova versão também é útil poder gerar um patch das mudanças locais, aplicá-lo sobre uma instalação limpa e verificar conflitos com um merge de 3 vias
      https://etckeeper.branchable.com/
    • Colocar usuários do sudo no grupo wheel para que apenas usuários com sudo possam ler e executar o arquivo parece bem razoável. Fico curioso por que isso não é a configuração padrão em todos os lugares
    • Desculpem a ignorância. Gostaria que alguém explicasse o que é o grupo wheel e o que ele faz. Sei que isso pode abrir uma discussão complexa
    • Não seria possível tornar /usr/bin/sudo immutable? Acho que isso ajudaria a impedir que o gerenciador de pacotes mexesse nele
    • Para a parte de não conseguir limitar o acesso root a comandos específicos, existe a infraestrutura de ForcedCommand
  • Não é isso que o systemd run0 faz hoje? Há uma nova ferramenta no systemd chamada run0, mas na verdade ela não é exatamente uma ferramenta totalmente nova; é mais um modo em que o antigo systemd-run, quando chamado por um link simbólico com o nome run0, se comporta como um substituto do sudo
    A diferença central é que, de fato, ele não é SUID. Ele pede ao gerenciador de serviços que execute um comando ou shell com o UID do usuário de destino, aloca um novo PTY e então encaminha dados entre o TTY original e esse PTY
    Em outras palavras, o comando de destino não herda o contexto do cliente; ele roda em um contexto de execução isolado, recém-forkado a partir do PID 1. $TERM é repassado, mas isso é uma exceção explícita, mais próxima de uma lista de permissões do que de uma lista de bloqueio
    Em vários aspectos, dá para dizer que run0 se comporta mais como ssh do que como sudo
    https://mastodon.social/@pid_eins/112353324518585654

    • Por que o systemd precisa refazer tudo?
  • Será que estou deixando passar alguma coisa? Não entendo como fazer login SSH como root seria mais seguro do que usar sudo. Sempre aprendi que isso não deve ser permitido, então também não sei ao certo o quão perigoso isso realmente é
    Aqui parece haver uma preocupação em bloquear usuários remotos, então não estou falando desse aspecto de segurança
    Pode ter relação com a limitação nº 3 do sudo, mas, pelo menos em comparação com a nº 1, não vejo vantagem
    Entendo que é um experimento, mas parece que seria mais vulnerável, não menos, do que o sudo. Um proxy de socket aberto parece vulnerável a ataques man-in-the-middle
    Ainda assim, aprendi algumas técnicas que dá para fazer com ferramentas antigas, e foi bom ver algo novo

    • O binário sudo é suid root e, por ser um binário privilegiado, fica diretamente exposto a usuários não confiáveis. Se algo der errado dentro do sudo, todo o ambiente do usuário vira superfície de ataque e pode ser explorado
      A abordagem com ssh não expõe um binário suid e usa a camada de rede do ssh. Portanto, não é menos segura do que acessar ssh pela rede, algo considerado bastante seguro
    • Uma das grandes vantagens do sudo é que, em vez de simplesmente abrir algo como sudo bash, você pode executar comandos individuais com sudo e aumentar a auditabilidade
    • Fazer login SSH como root, no texto, significa usar um servidor SSH adicional escutando em um socket Unix. O modelo de ameaças comum de expor login root à internet pode não se aplicar aqui
    • Essa abordagem está comparando erros de configuração teóricos ou vulnerabilidades que podem ou não existir com uma nova superfície de ataque criada pela execução de um novo daemon
      Esse daemon também pode ter erros de configuração ou vulnerabilidades, e reduz várias camadas de autorização baseadas em usuário a um único nível root
      Mesmo assim, parece ser considerada mais segura. De uma perspectiva de segurança razoável, não dá para chamá-la de mais segura; é apenas uma abordagem diferente
    • Sou cético quanto à abordagem do artigo linkado, mas a frase “login SSH remoto direto como root é perigoso” tem um componente de medo, incerteza e dúvida
      Na prática, fazer login SSH diretamente como root é estritamente mais seguro do que entrar como usuário via SSH remoto e depois usar sudo
      Se user@home faz ssh para root@server, root@server só é comprometido se user@home for invadido
      Por outro lado, se user@home faz ssh para user@server e depois usa sudo para virar root@server, basta que user@home ou user@server seja invadido para que o root seja comprometido. Em especial, em user@server, outros softwares como daemons ou tarefas cron costumam estar rodando
      Não se deve dar a alguém que conseguiu infectar por esse caminho uma escalada de privilégios root de graça, nem o movimento lateral que ocorre com frequência por causa da reutilização de senhas
      Claro, isso não se aplica quando o sudo é usado em modo restrito a comandos em lista de permissões e não recebe senhas ou credenciais totalmente acessíveis a partir do host remoto
  • O que acontece se o ssh não iniciar durante o boot? Pelo que lembro, numa configuração comum ele começa dependendo da rede. Então também não dá para fazer login nem no console failsafe
    Não sei o que se ganha de fato em comparação com sudo ou su. Em vez de evitar um binário setuid, você acaba executando um serviço de rede como root, mesmo que ele só se conecte a um socket

    • Falando como alguém que usa uma configuração parecida: faço isso no meu desktop principal. Se acontecer o pior do pior, tenho backup de tudo e reinstalo o sistema
      E se o SSD morrer? Nesse caso também não dá para fazer login no console failsafe
      Em 30 anos usando Linux, discos rígidos morreram muito mais vezes do que o daemon sshd deixou de iniciar; em termos de proporção, é quase uma divisão por zero
      Se o daemon sshd do sistema operacional parar de iniciar aleatoriamente, eu consideraria isso um sinal de que é hora de migrar para um sistema operacional mais estável
      O que se ganha em relação a sudo ou su é que exploits locais de escalonamento de privilégios ficam muito mais difíceis
    • O console Linux — isto é, o tty exibido no monitor local ou num KVM remoto, e os dispositivos ttyS* de porta serial e IPMI SoL — não usa sudo nem su
      Esses consoles usam programas como getty ou gerenciadores de janelas, que são programas não suid iniciados como root
      Faz sentido manter uma senha de root configurada para login pelo console
    • No meu caso, uso setuid/sudo por causa dos logs de auditoria. Hoje quase não opero mais caixas multiusuário/multisserviço, e a maior parte do que é multitenant está em k8s, então dá para usar o endpoint do kubectl em vez de ssh
      Se alguém consegue fazer login, deixo essa pessoa poder fazer setuid para root. Se for uma caixa k8s, isso é a equipe de infraestrutura da plataforma, e o acesso aos serviços em cima dela passa pelo provedor de permissões do k8s
      Do ponto de vista da equipe de infraestrutura da plataforma, se só forem necessários métricas e logs, eles já estão fora da caixa; se for preciso disparar alguma tarefa ou workflow, usa-se um pipeline
      Ainda assim, se alguém fizer login e executar trabalho como root, quero que fique um log de auditoria
      Não consigo pensar em nenhuma caixa minha em que alguém com permissão de login não tenha privilégios root completos
      Claro que entendo casos em que um serviço faz setuid, mas, no caso de serviços, normalmente o systemd faz setuid para reduzir privilégios, não para elevá-los
    • Se você tiver acesso ao bootloader, ainda pode definir systems.unit=emergency.target, init=/bin/bash, rd.break=pre-pivot ou inicializar por um ambiente de live CD. Todas as opções comuns de recuperação de emergência continuam funcionando
      Numa emergência menos catastrófica, também não vejo motivo para essa instância do sshd precisar estar vinculada à rede
  • É uma pena que o texto não inclua todas as desvantagens dessa abordagem. O sudo permite controlar quais grupos podem executar quais comandos, quais argumentos esses comandos podem receber, se é permitido criar subshells etc.
    Esse método perde muito desse controle granular e depende de chaves confiáveis que são mais difíceis de administrar do que editar o arquivo sudoers
    Recomendo muito o livro Sudo Mastery para ver as coisas surpreendentes que o sudo consegue fazer

    • O SSH também consegue fazer parte disso com ForceCommand, mas concordo que não é tão flexível nem tão preciso
  • A ideia é parecida com o run0 do Systemd: https://news.itsfoss.com/systemd-run0/

    • E o run0 não é uma gambiarra caseira. Ele foi auditado e provavelmente é melhor do que algo feito por conta própria
  • Um dos problemas do ssh é que a criação de processos não faz parte do protocolo. E, por ser um protocolo remoto, não dá para passar recursos locais para o processo filho
    Por isso não é possível passar um array de argumentos separado por null, passar descritores de arquivo adicionais ou especificar o executável
    Em vez disso, ele passa uma única string para o shell configurado no servidor. Isso exige escape de shell, e você também precisa saber qual shell está rodando no lado do servidor
    Para usar SSH como um substituto adequado do sudo, seria necessária uma extensão com algo próximo de posix_spawn

  • Concordo totalmente. Eu faço algo parecido e já expliquei isso antes em um comentário no HN
    Minha abordagem é um pouco diferente. Uso uma máquina dedicada como console SSH físico, e essa máquina fica numa LAN privada separada das outras máquinas de casa. Não é um switch gerenciável, é um switch comum; usa cabos Ethernet e não há trunk
    O login só é possível por SSH, e conectei uma Yubikey a ela
    O PC desktop tem seu próprio firewall e só permite tráfego SSH vindo do endereço IP/MAC desse console SSH. Isso vale apenas nessa LAN privada compartilhada entre os dois; em outra LAN física, o desktop consegue acessar a internet
    O daemon sshd permite apenas login por chave pública/privada e bloqueia login por senha
    Quando preciso de root, inicializo o “console SSH”. Como é uma máquina com quase nada, liga muito rápido. Faço login, uso a seta para cima para recuperar a linha ssh root@..., pressiono Enter e toco na Yubikey
    Esse console SSH e o teclado ficam na mesa, sempre ao alcance da mão
    Se iptables/nftables + sshd numa LAN privada, ainda por cima fisicamente separada de outras LANs privadas, é mais ou menos seguro do que o binário sudo ou o su, cada um que julgue
    Se perguntarem “por quê?”, minha resposta é “porque dá”. Configurei isso há tanto tempo que nem lembro quando foi. Provavelmente comecei a brincar com essa ideia uns 2 anos atrás e venho usando desde então. Nunca tive problema algum

    • A configuração não é exatamente igual, mas parece semelhante ao conceito de bastion host
  • É uma solução elegante para esse problema. Não é preciso tratar usuários como crianças, mas ao mesmo tempo é bom evitar possíveis erros com padrões razoáveis
    Se precisar de root, basta fazer login como root no console, então acho que nem su é necessário. Essa abordagem é o mais próximo possível de fazer login como root num tty de console

    • O método de “se precisar de root, faça login como root no console” tem dois problemas
      Primeiro, ao contrário do sudo, todos os usuários precisam saber a senha de root
      Segundo, se todo mundo simplesmente fizer login como root, não há como auditar quem realmente entrou e fez o quê
  • Fiz algo parecido há 10 anos. Não havia a parte de socket UNIX; eu rodava um sshd separado que escutava apenas no localhost, e também não precisei lidar com SCM_RIGHTS
    Não houve nenhum resultado particularmente bom ou ruim; simplesmente perdi o interesse e não levei a configuração para a máquina seguinte