6 pontos por GN⁺ 2023-08-24 | 1 comentários | Compartilhar no WhatsApp
  • SSH é uma ferramenta que vai além do acesso remoto, cobrindo também encaminhamento de portas, jump hosts, arquivo de configuração e gerenciamento de chaves; os exemplos com servidor web podem ser aplicados da mesma forma a outros serviços como RDP e SQL
  • Encaminhamento de porta local, remoto e dinâmico são configurados com -L, -R e -D, respectivamente, e a principal diferença é em que lado a porta é aberta e para onde o tráfego flui
  • Em redes onde o acesso direto é bloqueado, é possível chegar ao destino conectando vários intermediários SSH com jump hosts via -J e ProxyJump
  • ssh-agent e o encaminhamento de agente com -A permitem usar suas chaves locais também em hosts remotos, o que é conveniente, mas primeiro é preciso avaliar os riscos de segurança do uso indevido do agente
  • Usar ~/.ssh/config, ssh-copy-id, ssh-keygen e o console do SSH ~?·~C em conjunto reduz a repetição de opções, facilita adicionar forwards durante a sessão, distribuir chaves públicas e gerar ou inspecionar chaves

Premissas para entender o encaminhamento de portas no SSH

  • O encaminhamento de portas no SSH é difícil de entender só com diagramas; fica mais fácil quando se vê os comandos reais junto com cenários de rede
  • Os exemplos usam acesso a servidor web, mas a mesma abordagem pode ser aplicada a RDP, SQL e praticamente qualquer serviço
  • As opções usadas repetidamente têm os seguintes significados
    • -N: não executa comando no servidor remoto e também não abre shell
    • -f: envia o SSH para o background
    • root@host: faz login no host com o usuário que criará o túnel

Encaminhamento de porta local -L

  • O encaminhamento de porta local redireciona uma porta da máquina local para uma porta no servidor remoto
  • Situação de exemplo
    • internal-web.int hospeda uma página web acessível apenas pela interface de loopback
    • campfire.int consegue acessar internal-web.int via SSH
    • Deseja-se acessar o servidor web de internal-web.int por meio de uma porta local em campfire.int
  • Comando usado
    • ssh -N -f -L 1337:127.0.0.1:80 root@internal-web.int
  • Interpretação das opções
    • -L: define encaminhamento local
    • 1337:127.0.0.1:80: vincula a porta local 1337 ao 127.0.0.1:80 remoto
  • Depois que o túnel é criado, é possível enviar requisições para a porta local 1337 em campfire.int e interagir com a porta 80 de internal-web.int
  • O ponto a lembrar é que, em -L, a porta local fica à esquerda no endereço

Encaminhamento de porta remoto -R

  • O encaminhamento de porta remoto expõe, no servidor remoto, uma porta acessível localmente
  • Situação de exemplo
    • internal-web.int hospeda uma página web acessível apenas por loopback
    • campfire.int não consegue acessar internal-web.int diretamente por causa do firewall
    • vuln-server.int pode ser acessado tanto por campfire.int quanto por internal-web.int
  • Comando usado
    • ssh -N -f -R 3000:127.0.0.1:80 root@vuln-server.int
  • Interpretação das opções
    • -R: define encaminhamento remoto
    • 3000:127.0.0.1:80: vincula a porta 3000 de vuln-server.int ao 127.0.0.1:80 local
  • Depois disso, ao enviar uma requisição curl para vuln-server.int:3000, é possível acessar a página web interna que roda na porta 80 de internal-web.int
  • O ponto a lembrar é que, em -R, a porta local fica à direita no endereço

Encaminhamento de porta dinâmico -D e proxy SOCKS

  • O encaminhamento de porta dinâmico cria um proxy SOCKS com a opção -D e envia o tráfego por um intermediário SSH
  • Situação de exemplo
    • internal-web.int hospeda uma aplicação web acessível apenas pela rede interna
    • vuln-server.int está na mesma rede interna e consegue acessar internal-web.int
    • Deseja-se fazer proxy do tráfego a partir de campfire.int por meio de vuln-server.int
  • A configuração de /etc/proxychains.conf precisa corresponder à porta do comando SSH
    • socks5: faz o proxychains usar SOCKS5
    • 127.0.0.1: usa localhost
    • 8080: deve corresponder à porta definida em -D no SSH
  • Comando usado
    • ssh -N -f -D 8080 root@vuln-server.int
  • Depois de criar o forward, ao configurar socks5 127.0.0.1 8080, é possível acessar a página web interna com proxychains curl 192.168.1.185
  • O DNS via SOCKS pode não funcionar bem dependendo do ambiente, por isso o exemplo usa endereço IP em vez de nome de host
  • No Firefox também é possível usar o proxy SOCKS com configuração manual
    • Caminho: Settings → Privacy & Security → Network Settings
    • Selecionar Manual proxy configuration
    • Marcar “Proxy DNS when using SOCKS V5”
    • Definir SOCKS host como 127.0.0.1 e port como 8080

Jump host -J

  • Um jump host permite se conectar a um destino que não é acessível diretamente a partir do host atual, passando por vários intermediários SSH
  • A cadeia de exemplo é campfire.intvuln-server.intinternal-web.intdns.int
  • Comando usado
    • ssh -J root@vuln-server.int,root@internal-web.int root@dns.int
  • Vários destinos de salto são separados por vírgula

Encaminhamento de agente -A

  • ssh-agent permite adicionar uma chave privada ou identidade na máquina local com ssh-add <private_key_file>
  • As chaves adicionadas podem ser verificadas com ssh-add -l
  • Ao adicionar uma chave ao ssh-agent, é possível fazer conexões SSH com essa chave sem redigitar a senha, o que é útil tanto para pessoas quanto para contas de serviço
  • O encaminhamento de agente com -A permite usar, na máquina remota à qual você se conectou, as chaves que estão no agente local
  • Antes de usar, é preciso verificar os riscos de segurança em Zero Effort Private Key Compromise: Abusing SSH-Agent for Lateral Movement
  • Comando de exemplo
    • ssh -A -J root@vuln-server.int root@internal-web.int
  • Esse comando acessa internal-web.int passando por vuln-server.int e permite usar as chaves do agente SSH local de campfire.int
  • Depois disso, ao executar ssh root@dns.int em internal-web.int, é possível se conectar sem especificar chave privada nem informar credenciais

Alocação de TTY -t

  • A opção -t é útil para executar rapidamente no servidor remoto comandos que exigem interação
  • O exemplo são comandos que precisam de TTY, como Vim ou top
  • Comando usado
    • ssh root@internal-web.int -t top
  • Ao executar, você recebe um TTY no servidor remoto com o comando top

Permitir que hosts externos também usem a porta do forward local com -g

  • A opção -g permite que hosts remotos se conectem à porta encaminhada localmente
  • É parecida com o encaminhamento local -L, mas com a diferença de que máquinas externas também podem usar essa porta “local”
  • Situação de exemplo
    • Há acesso shell a vuln-server.int
    • Deseja-se fazer proxy das conexões que chegam à porta 2222 de vuln-server.int para a porta 22 de internal-web.int
  • Comando usado
    • ssh -N -f -g -L 2222:localhost:22 root@internal-web.int
  • Interpretação das opções
    • -g: permite que hosts remotos se conectem à porta de encaminhamento local
    • -L: define encaminhamento local
  • Mesmo conectando por SSH à porta 2222 de vuln-server.int, o shell real estará em internal-web.int

Console do SSH ~? e encaminhamento durante a sessão

  • O console do SSH é um recurso oculto que permite controlar o próprio SSH sem interagir diretamente com o sistema remoto
  • É útil quando o shell travou ou quando é preciso controlar a própria sessão SSH
  • O console de ajuda pode ser aberto com ~?
  • Opções úteis
    • ~.: encerra a sessão SSH atual
    • ~C: abre o console do SSH para adicionar opções de encaminhamento
  • Mesmo já conectado a vuln-server.int com um comando ssh comum, ao pressionar ~C e digitar -D 8080, é possível passar a usar essa sessão como uma sessão de encaminhamento dinâmico
  • Se /etc/proxychains.conf em campfire.int estiver configurado para usar a porta 8080, será possível usar o proxychains como se a sessão tivesse sido iniciada com ssh -D desde o começo

Arquivo de configuração do SSH ~/.ssh/config

  • O arquivo de configuração do SSH fica em ~/.ssh/config e permite salvar opções usadas com frequência para economizar tempo
  • Ao iniciar uma conexão SSH, esse arquivo é analisado e, se houver uma configuração host compatível com o destino, essas opções serão usadas
  • Argumentos de linha de comando têm prioridade sobre o arquivo de configuração
    • Por exemplo, mesmo que o arquivo defina o usuário de internal-web.int como root, ao executar ssh graham@internal-web.int a tentativa de login será com graham
  • Exemplo de configuração básica
# You can put comments with a `#` at the beginning of the line only.
host internal-web.int
    User root
    IdentityFile /home/smores/ssh_agent/internal-web-no-pw
    Port 2222
  • Fluxo de processamento ao executar ssh internal-web.int
    • Faz a correspondência entre internal-web.int na linha de comando e host internal-web.int em ~/.ssh/config
    • Se houver correspondência, busca no arquivo de configuração as opções não especificadas na linha de comando
    • Se não houver correspondência, usa apenas as opções definidas na linha de comando

Palavras-chave comuns do SSH config

  • IdentityFile /path/to/private_key
    • Define a chave privada a ser usada para o host
    • Equivale a ssh -i
  • ForwardAgent
  • ProxyJump root@internal-web.int
    • Define o servidor pelo qual o tráfego será proxied
    • Equivale à opção -J
    • No exemplo, isso mostra que o tráfego passa por vuln-server.int, pois a autenticação nesse host é exigida primeiro
  • Match
    • Aplica palavras-chave do SSH config com base em condições
    • No exemplo, se o código de saída do comando export | grep PROXYME=TRUE for 0, o ProxyJump abaixo do bloco Match será usado
    • Se a variável de ambiente PROXYME não existir, apenas o bloco normal host internal-web.int será usado
    • Após definir export PROXYME=TRUE, ao executar o mesmo ssh internal-web.int, você passará pela autenticação em vuln-server.int antes de receber o shell de internal-web.int
  • scp e alguns utilitários baseados em SSH normalmente também podem usar o SSH config
    • Em ambientes que não o usam automaticamente, é possível especificar explicitamente com -F ~/.ssh/config

Cópia de chave pública ssh-copy-id

  • ssh-copy-id é um pequeno utilitário para enviar rapidamente uma chave pública ao servidor
  • Comando usado
    • ssh-copy-id -i internal-web root@internal-web.int
  • Interpretação das opções
    • -i internal-web: define o nome da chave privada a ser usada na autenticação com o servidor
    • root@internal-web.int: define o servidor para o qual a chave pública será enviada

Geração e inspeção de chaves ssh-keygen

  • ssh-keygen é o utilitário para gerar pares de chave privada e pública
  • Em geral, recomenda-se usar a opção -b para especificar um tamanho de chave maior
  • No ambiente de exemplo, o tamanho padrão da chave era 3072
  • O algoritmo padrão é RSA, mas é possível escolher outro com a flag -t
    • Exemplo: ssh-keygen -t ecdsa -b 521
  • A impressão digital e o tamanho em bytes da chave podem ser verificados com o comando abaixo
    • ssh-keygen -lf <file-name>

1 comentários

 
GN⁺ 2023-08-24
Comentários do Hacker News
  • Falta aqui uma diretiva surpreendentemente simples: basta configurar algo como AuthorizedKeysCommand /usr/bin/php /etc/ssh/auth.php %u em sshd_config e, no script, buscar https://github.com/{$user}.keys no GitHub
    Claro, não é código com qualidade de produção, mas mostra a ideia central
    Depois de verificar se a pessoa pertence a uma organização/grupo do GitHub, se o usuário existir e estiver mapeado por algo como nss-ato, você pode permitir login no servidor
    Ao fazer onboarding/offboarding de pessoas, basta adicioná-las/removê-las de um grupo do GitHub para conceder ou revogar acesso aos equipamentos, reduzindo o trabalho

    • O Amazon Linux faz algo parecido, mas provavelmente é muito mais complexo por ser qualidade de produção
      No Amazon Linux 2 e anteriores, ele chama a linha de comando do openssl para verificar o formato de cada chave no arquivo authorized_keys, mas isso é hardcoded para RSA; então, mesmo que a versão do OpenSSH suporte ed25519, não era possível autenticar em hosts Amazon Linux 2 usando ed25519
      Em teoria, é bom porque permite um recurso bacana¹, mas, na prática, quebra a funcionalidade básica até para quem não usa esse recurso, o que me faz confiar menos no Amazon Linux
      Quando encontrei esse problema pela primeira vez, eu estava tentando acessar via SSH uma máquina de um colega de DevOps cloud-first e, como não podia mexer diretamente nela, foi difícil diagnosticar
      Ele conhecia bem a AWS, mas não tanto Linux, então não sabia onde procurar; escolheu Amazon Linux por ser uma distribuição feita pelo dono da plataforma de nuvem e achou que seria “mais compatível”, mas aqui “mais compatível” na verdade significava “com mais surpresas idiotas”
      ¹ https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/connect-...
    • Ver coisas assim me faz lamentar estar na área de redes
      Como “a rede não está funcionando direito” é o escopo do trabalho, acabo perdendo esses recursos legais
    • Para esse tipo de uso, é melhor usar certificados SSH em vez disso
  • Muita gente provavelmente não sabe que o parser de configuração do OpenSSH ignora diretivas duplicadas e que, entre diretivas iguais, só a primeira tem efeito
    Isso é bem contraintuitivo, porque em parsers de configuração ou motores de regras normalmente a diretiva posterior sobrescreve a anterior
    Pode parecer um detalhe pequeno, mas, ao mudar valores padrão como os de /etc/ssh/sshd_config, pessoas e softwares tendem a acrescentar as mudanças ao fim do arquivo ou do bloco de diretivas, esperando que elas sejam aplicadas
    Empresas e organizações de segurança, vários produtos de bastion SSH, também erram isso, e as recomendações do CIS Benchmarks e a maioria das ferramentas terceiras de auditoria CIS não consideram a prioridade ou a tratam incorretamente
    Para verificar se as diretivas de configuração do OpenSSH foram definidas como esperado, não examine manualmente o arquivo de configuração; despeje a configuração interna derivada com sshd -T ou ssh -G

    • O arquivo sudoers funciona da mesma forma
      Em software de autenticação/autorização de usuários, considero esse comportamento desejável
      Isso porque fica fácil criar configurações que não podem ser sobrescritas simplesmente adicionando um novo arquivo em um diretório whatever.conf.d
      Basta definir essa configuração no arquivo principal antes de carregar whatever.conf.d/* e aplicar proteções especiais a esse arquivo
      Mesmo sem alguém tentando burlar controles, isso é vantajoso para gerenciamento de configuração, porque a configuração-base mantém prioridade mesmo que uma pessoa novata, sem entender todo o contexto, adicione um novo arquivo, ou que algum pacote instale padrões estranhos para seu próprio serviço
      O motivo de vermos o comportamento oposto com mais frequência em outros contextos é que o que se quer não é uma “configuração-base”, mas sim valores padrão no sentido estrito, usados quando usuários/desenvolvedores/administradores não configuraram algo explicitamente
    • Algumas diretivas parecem poder ser duplicadas. Por exemplo, AllowUsers
      Só que ontem o NixOS mudou de repente a ordem de mesclagem, e meu AllowUsers acabou ficando abaixo de um Match em outro arquivo, me deixando bloqueado
  • A frase mais importante e concisa do texto é a explicação para lembrar que, no encaminhamento local com -L, o local fica à esquerda do endereço, e, no encaminhamento remoto com -R, a porta local fica à direita do endereço
    Desde o começo eu me confundia com -L e -R, e é bem irritante que a instância de porta que é local mude conforme L/R
    Entendo que -L e -R mudam a direção pretendida, ou seja, onde estão o iniciador e o respondente, mas acho que também teria sido razoável fazer port:address:port significar sempre local:binding:remote e deixar -L/-R determinar qual lado escuta e qual lado envia

    • A forma mais fácil de lembrar é: -L escuta na porta local cujo número vem logo depois, e -R escuta na porta remota cujo número vem logo depois
      O restante, host:port, é apenas o formato comum que informa para onde conectar
      Como se trata de encaminhamento de porta por meio de um túnel SSH, também decorre naturalmente que o host é contatado do lado oposto do túnel em relação ao lado onde está a porta de escuta
  • Um recurso pouco conhecido, mas útil, do SSH é a multiplexação de conexões
    Em vez de criar uma nova conexão TCP e passar de novo pelo processo de autenticação, você pode reutilizar uma conexão existente
    O próprio protocolo tem o conceito de canais, e cada quadro de dados recebe metadados que distinguem diferentes streams; esse recurso usa isso
    A grande vantagem é que, nas sessões seguintes, não é preciso refazer toda a autenticação
    É especialmente bom quando não há tmux ou algo do tipo no remoto e você usa vários painéis em várias janelas de terminal; se a autenticação envolver etapas que levam alguns segundos, como uma frase secreta ou tocar em um HSM, dá para sentir ainda mais a diferença
    Também há uma configuração de “persistência de conexão”, para que você não precise se autenticar toda vez que alternar entre alguns servidores
    No geral, vejo como um recurso bom de ter, mas não a ponto de mudar a vida
    Em alguns servidores ele fica desativado; a ausência é mais perceptível quando está desativado do que a presença quando está ativado
    Veja mais: https://en.wikibooks.org/wiki/OpenSSH/Cookbook/Multiplexing

    • Se houver muita latência entre cliente e servidor, a diferença é enorme
      Ao contrário do TLS, que é otimizado para reduzir o número de idas e voltas, o SSH é um protocolo bem “falante”, e essa opção de multiplexação é praticamente a única exceção
    • Pode tornar o Ansible muito mais agradável de usar ao passar por um bastion host com ProxyJump
  • Se você tem muitos hosts em ~/.ssh/config, pode evitar que o arquivo fique bagunçado demais usando a diretiva Include, que aceita curingas
    Por exemplo, você pode colocar Include config.d/*.conf em ~/.ssh/config e separar as configurações de host, hostname e user em arquivos como ~/.ssh/config.d/work.conf ou client1.conf

    • A diretiva Host também aceita curingas
      Por exemplo, você pode adicionar host *_work e colocar ali configurações comuns a todos os hosts de trabalho, como host1_work
    • Outra dica é que dá para colocar Include dentro de diretivas Host/Match
      Por exemplo, se você tiver Host proj1.*.corp e Include ~/.ssh/proj1.conf em ~/.ssh/config, pode manter os matches por projeto perto do topo e reduzir a necessidade de vasculhar inúmeros arquivos individuais ao revisar
  • Ao fazer forwarding, quase nunca uso -f
    Ele pode virar uma arma apontada para o próprio pé, porque dificulta saber quais forwardings ainda estão abertos e em funcionamento
    -t é um truque bacana, e era um recurso que eu não conhecia
    Um ponto importante que é fácil deixar passar na lista de comandos de escape com ~ é que você também pode aninhar escapes dentro de sessões aninhadas
    Isso é útil quando, por algum motivo, você não usa -J
    A lista combina bem com coisas úteis, e também aprendi mais algumas

    • -t é excelente. Eu uso algo como ssh -t my-dev-vps 'tmux new-session -A -s main' para voltar direto ao ponto anterior da sessão tmux toda vez que executo
    • Mesmo quando você mantém vários shells abertos no servidor de destino, o mesmo problema continua existindo
      Seria bom se o SSH expusesse o estado do forwarding de uma forma razoável, em vez de obrigar a vasculhar manualmente a lista de processos
  • Há um pull request atual que adiciona suporte a AF_UNIX; se ele entrar, vai permitir todo tipo de forwarding interessante
    Isso porque ficará mais fácil fazer proxy de conexões SSH por meio de processos locais arbitrários, e esse processo poderá cuidar, como quiser, de encaminhar os dados até a outra ponta remota
    https://github.com/openssh/openssh-portable/pull/431

    • O que me interessa é -D usando AF_UNIX, mas é bom que tudo possa passar a funcionar sobre AF_UNIX
      Parece que, desde cerca de um ano atrás, o curl consegue usar SOCKS sobre AF_UNIX por meio da sintaxe ALL_PROXY socks5://localhost/path ou socks5h
      Aparentemente isso foi adicionado porque o Tor usa proxy SOCKS em AF_UNIX
      Seria bom poder configurar acesso à rede com permissões Unix padrão; pessoalmente, seria ainda melhor se desse para expulsar completamente o TCP/IP do kernel
  • Fiquei chocado quando vi o console do SSH pela primeira vez
    Um colega me mostrou ~#, e foi como descobrir um menu secreto de cheats que poderia aparecer em um jogo do SEGA Genesis

  • Por que til? Porque rlogin e rsh usavam
    Por que rlogin e rsh usavam til? Porque cu usava
    Por que cu? Quando havia um modem ou uma linha serial, você se comunicava com cu e precisava enviar códigos Hayes; se usasse a sequência de escape dos códigos Hayes, cairia no modem, então era necessário um sinal separado para escapar do cu
    Por que não ^[? Porque isso é do telnet
    Portanto, se você se conectasse a um host via telnet e depois usasse cu para se conectar a um modem, precisava de uma sintaxe de escape separada para voltar ao cu sem escapar para o telnet
    No fim, é uma estrutura de sintaxes de escape empilhadas sem fim
    E, na prática, não é til; é til

    • Eu achava que a sequência era CR, til, .; será que eu estava usando errado esse tempo todo?
  • A seção sobre ssh-copy-id começa explicando que o comando envia a chave pública, mas de repente passa a dizer que envia a chave privada; parece ser um erro de digitação
    Além disso, esse comando não apenas faz upload da chave: ele a acrescenta a ~/.ssh/authorized_keys, o que o torna muito mais útil
    Por fim, na seção sobre ssh-keygen, pelo que tenho lido ultimamente, ed25519 é preferido a ecdsa