1 puntos por GN⁺ 4 시간 전 | 1 comentarios | Compartir por WhatsApp
  • En las imágenes mínimas de contenedores, a menudo faltan curl o wget, así que resulta útil contar con una alternativa para verificar la conectividad hacia servicios internos sin instalar paquetes
  • La redirección /dev/tcp/host/port de Bash puede abrir un socket TCP, lo que permite escribir y enviar directamente una cadena de solicitud HTTP/1.1 y leer la respuesta
  • /dev/tcp no es una ruta del sistema de archivos, sino una función interna de Bash, por lo que no funciona con ls /dev/tcp ni con los métodos normales de acceso a archivos desde otros shells
  • Este método es una técnica de depuración simple que no maneja redirecciones, respuestas chunked, compresión, reintentos ni TLS, y sin Connection: close cat puede quedarse esperando
  • Para el trabajo diario con HTTP, lo correcto es usar curl, pero en contenedores pequeños donde es difícil agregar herramientas, esto basta para una verificación rápida de conectividad

Escribir una solicitud HTTP con descriptores de archivo de Bash

  • Había que comprobar si se podía acceder al endpoint /health de otro servicio dentro de una red interna de Docker, pero la imagen no tenía curl ni wget
  • Bash puede conectar un socket TCP a un descriptor de archivo, así que se puede escribir y enviar una solicitud HTTP directamente de esta forma
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 debe ser un nombre de host que pueda resolverse y alcanzarse desde donde se ejecuta
    • Puede ser el nombre de un contenedor o servicio configurado en la red de Docker
    • También puede usarse un nombre DNS resolvible
    • El host y el puerto deben ajustarse según el entorno
  • La salida de la respuesta incluye la línea de estado, los encabezados, una línea en blanco y el cuerpo
  • Para agregar encabezados, basta con insertar más líneas terminadas en \r\n antes de la línea en blanco final que cierra la solicitud
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 qué /dev/tcp no es un archivo real

  • /dev/tcp no es un archivo de dispositivo real, sino una redirección manejada por Bash
    • Como esa ruta no existe en disco, ls /dev/tcp falla
    • Si se ejecuta cat /dev/tcp/... desde otro shell, también dará error
  • Según el manual de Bash, en /dev/tcp/host/port, si host es un nombre de host válido o una dirección de internet y port es un número de puerto entero o un nombre de servicio, Bash intentará abrir un socket TCP
  • Bash realiza la consulta DNS y el connect(2), y exec 3<> conecta el socket al descriptor de archivo 3 para permitir lectura y escritura

No es un reemplazo de un cliente HTTP, sino una herramienta temporal de inspección

  • Este enfoque no es un cliente HTTP real, así que no maneja redirecciones, respuestas chunked, compresión, reintentos, TLS ni otros detalles
  • El encabezado Connection: close es importante
    • Sin él, el servidor puede mantener la conexión abierta según el comportamiento predeterminado de HTTP/1.1
    • En ese caso, cat <&3 puede quedarse esperando EOF y no terminar
  • Envolverlo con algo como timeout 6 bash -c '...' también ayuda a cubrir casos en que la conexión no se cierra
  • /dev/tcp abre un socket sin procesar, así que solo aplica a HTTP en texto plano; para https se necesita openssl s_client
  • No es una función POSIX, sino de Bash, así que no puede usarse en dash, que es /bin/sh en Debian, ni en zsh; hay que invocar bash directamente
  • Es una opción de compilación que se habilita con --enable-net-redirections al compilar Bash
  • En resumen, más que una herramienta general para reemplazar curl, sirve para comprobar rápidamente la conectividad en contenedores pequeños donde no se puede agregar instalación adicional

1 comentarios

 
GN⁺ 4 시간 전
Opiniones de Hacker News
  • De niño, a fines de los 90, me impactó descubrir que podía conectarme con telnet a los puertos 80, 25 y 110 y hablar directamente con el servidor
    Podía escribir a mano una solicitud simple como GET / HTTP/1.1, o enviar correos en el puerto 25 con HELO, mail-from y mail-to, y también obtener la lista del buzón y mensajes individuales por POP3
    Esa experiencia fue el inicio de darme cuenta de que “no hay magia”, y de entender que todas las partes de una computadora fueron hechas por personas y que, con esfuerzo, se pueden comprender hasta cierto nivel
    En el futuro la mayoría lo dejará en manos de agentes, pero para quien quiera aprender cómo funcionan realmente las cosas sin los filtros de modelos y salvaguardas, probablemente seguirán quedando huecos interesantes en varios sistemas

    • En la época en que no existían DKIM/SPF y la autenticación de los servidores SMTP era más laxa, podías mandar un correo usando una dirección como jacques.chirac@elysee.fr y parecer hacker ante tus amigos
    • En ese entonces no solo no existían DKIM ni SPF, sino que la mayoría de los servidores SMTP eran open relays que aceptaban correo de cualquiera para cualquiera
    • Al final, todo son archivos de texto
      Era una estructura con montones de siglas encima de distintas formas de crear, enviar y leer archivos de texto estructurados
      Un día me di cuenta de que hasta las bases de datos eran archivos de texto y tuve que sentarme un rato
    • En el siglo pasado, en la empresa leíamos y enviábamos correo personal conectándonos por telnet a POP3 y SMTP respectivamente
    • Con HTTP/2 no se puede hacer así, pero por suerte casi todos los servidores todavía hablan HTTP/1
      Con telnet tampoco sirve TLS, y muchos servidores solo devuelven redirecciones para solicitudes HTTP
      Si usas openssl s_client en lugar de telnet, sí puedes tunelizar texto dentro de TLS, aunque se siente un poco como una trampa
      También da pena que muchos protocolos modernos prefieran codificación binaria, lo que hace más difícil manosearlos a nivel de línea sin herramientas especializadas
      Aun así, parece probable que en el futuro siga habiendo gente que se meta a fondo en estas cosas; como encender fuego con un palo o cocer ladrillos de barro, las técnicas antiguas son divertidas y a veces realmente útiles
      De hecho, gracias a la IA ahora es más fácil experimentar, porque puedes preguntarle a un LLM y aprender, por ejemplo, la mayoría de los comandos IMAP comunes sin tener que ponerte a revisar RFCs
  • zsh tiene, aparte de /dev/tcp de Bash, los módulos zsh/net/tcp y 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....

  • En Plan 9 existía /net, que era un sistema de archivos sintético real, así que desde cualquier programa podías hacer este tipo de cosas y más
    También podías montar el /net de otra máquina mediante el protocolo 9P y usarlo como una especie de VPN improvisada, y se puede experimentar con 9front desde Linux
    En las bibliotecas de Go también se ven rastros del /net al estilo Plan 9, parece parte del legado de Rob Pike

  • Funciona bien con example.com
    Si lo abres con exec 3<>/dev/tcp/example.com/80, luego envías printf 'GET / HTTP/1.1\r\nHost: example.com\r\nConnection: close\r\n\r\n' >&3 y después haces cat <&3, aparece HTTP/1.1 200 OK
    Hoy en día quedan tan pocos dominios que no fuercen HTTPS que, para este tipo de pruebas, uno termina yendo a example.com

    • example.com también sirve cuando se descompone un portal cautivo de WiFi público
      Si en el navegador vas a http://example.com, te redirige otra vez a la página del portal cautivo y puedes completar de nuevo el proceso para obtener acceso a internet
    • También funciona si pones saltos de línea reales dentro de printf
      Lo correcto es incluir \r, pero incluso si lo omites igual funciona
  • También se puede hacer la broma de que, para hablar con la computadora de un amigo, todos usan bash -i >& /dev/tcp/IP/PORT 0>&1.

  • No es que Bash hable HTTP, sino que te permite abrir sockets TCP
    Lo que se hace aquí es hablar HTTP directamente, y para pruebas o depuración está bien, incluso es divertido hacerlo a mano, pero usar un cliente HTTP improvisado como este en un entorno real no supervisado es buscarse problemas
    Este código de juguete puede romperse porque no parsea HTTP correctamente
    Claro que también se puede escribir un cliente HTTP/1.1 completo en Bash, e incluso hacer un servidor HTTP en Bash puro: https://github.com/bahamas10/bash-web-server
    Una opción menos loca suele ser nc, y por lo general es una decisión más sensata

    • La expresión “servidor HTTP completo en Bash puro” en rigor es incorrecta
      Bash no puede escuchar sockets TCP/UDP para aceptar conexiones entrantes
      El proyecto bash-web-server compila un listener de sockets en C y luego lo carga dinámicamente en tiempo de ejecución como módulo “integrado” para proporcionar esa funcionalidad
      [0] https://github.com/bahamas10/bash-web-server/tree/main/loada...
    • La observación es correcta, y como la redacción del post fue exagerada, pienso actualizarla para que sea más precisa
      nc o alguna herramienta similar de la familia netcat sería una mejor opción, pero la imagen que estaba usando en ese momento no tenía ninguna de esas herramientas
    • Tampoco está tan loco
      Llevo escribiendo solicitudes HTTP a mano desde antes de HTTP/1.1 y de que existiera el encabezado obligatorio Host
      Usarlo para algo serio sí sería una locura, igual que implementar un servidor web en Bash, pero para pruebas rápidas funciona bastante bien
    • Alguien incluso hizo un servidor de Minecraft en Bash puro
      https://sdomi.pl/weblog/15-witchcraft-minecraft-server-in-ba...
    • También hay un framework tipo Rails para Bash: https://github.com/jneen/balls
  • Aprendí esto viendo al equipo de Bauhinia usarlo al resolver un reto de CTF
    Era un CTF de varias etapas, y al principio conseguían una shell system con una cadena ROP, pero en la práctica era un entorno tipo cárcel donde no se podía ejecutar casi nada salvo Bash
    Lo único disponible era algo como read y cat, así que usaron cat /dev/tcp, luego lo redirigieron a un terminal virtual, leyeron su contenido, obtuvieron una URL interna del sistema y así encontraron la flag

  • Descubrí este método mientras verificaba la conectividad entre contenedores dentro de una red interna de Docker, porque la imagen no tenía ni curl ni wget
    Lo sorprendente fue ver que Bash tiene /dev/tcp, así que con un poco de magia de shell se puede armar algo parecido a una solicitud HTTP
    Por ejemplo, puedes abrir exec 3<>/dev/tcp/service/8642, enviar printf 'GET /health HTTP/1.1\r\nHost: service\r\nConnection: close\r\n\r\n' >&3 y luego hacer cat <&3
    Aquí service es el nombre del host al que te conectas y 8642 es el puerto al que intentas hablarle por HTTP

    • Está bueno, pero me pregunto si hay alguna desventaja en simplemente usar una imagen con soporte para curl
      No se me ocurre ninguna, y de hecho lo considero casi indispensable incluso en imágenes de producción
  • En Debian antiguo y en distribuciones derivadas de Debian esto no funcionaba, porque el acceso TCP mediante archivos virtuales venía desactivado por defecto
    Según entiendo, en 2009 cambiaron de postura y habilitaron la función, y en el Bug #146464 hay discusión y enlaces
    <https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=146464#37>
    Además de esto, también hay varias formas de acceder directamente a funciones de red desde herramientas de shell, como curl, wget, los comandos HEAD y GET de Perl, netcat/nc, socat, telnet, etc.

  • Recuerdo que de adolescente asustaba a otros enviando mensajes inquietantes con echo al /dev/ptty de otra persona
    El mensaje que yo enviaba aparecía como por arte de magia en su terminal abierta
    Todavía no entiendo por qué en la sala de cómputo usaban cuentas distintas por cliente y no las bloqueaban; quizá era una limitación de los VAX en esa época