1 points par GN⁺ 4 시간 전 | 1 commentaires | Partager sur WhatsApp
  • Dans les images de conteneur minimales, curl ou wget sont souvent absents, donc une méthode de contournement pour vérifier la connectivité à des services internes sans installer de paquet peut être utile
  • La redirection Bash /dev/tcp/host/port permet d’ouvrir un socket TCP, afin d’écrire directement une chaîne de requête HTTP/1.1 et de lire la réponse
  • /dev/tcp n’est pas un chemin du système de fichiers mais une fonction interne de Bash ; cela ne fonctionne donc pas avec ls /dev/tcp ni avec les approches d’accès à des fichiers ordinaires dans d’autres shells
  • Cette méthode est une technique de débogage simple qui ne gère ni les redirections, ni les réponses chunked, ni la compression, ni les nouvelles tentatives, ni TLS, et sans Connection: close, cat peut rester en attente
  • Pour les tâches HTTP du quotidien, curl est le bon outil, mais dans de petits conteneurs où il est difficile d’ajouter des outils, cela suffit pour une vérification rapide de la connectivité

Écrire une requête HTTP avec un descripteur de fichier Bash

  • Il fallait vérifier si le point d’entrée /health d’un autre service sur un réseau Docker interne était accessible, mais l’image ne contenait ni curl ni wget
  • Bash peut relier un socket TCP à un descripteur de fichier, ce qui permet d’écrire et d’envoyer directement une requête HTTP comme suit
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 doit être un nom d’hôte résolu et joignable depuis l’endroit où la commande est exécutée
    • Cela peut être le nom d’un conteneur ou d’un service défini sur le réseau Docker
    • Un nom DNS résolvable peut aussi être utilisé
    • L’hôte et le port doivent être adaptés à l’environnement
  • La sortie de la réponse inclut la ligne de statut, les en-têtes, une ligne vide et le corps
  • Pour ajouter des en-têtes, il suffit d’insérer d’autres lignes se terminant par \r\n avant la ligne vide qui termine la requête
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

Pourquoi /dev/tcp n’est pas un vrai fichier

  • /dev/tcp n’est pas un véritable fichier de périphérique, mais une redirection gérée par Bash
    • Comme ce chemin n’existe pas sur le disque, ls /dev/tcp échoue
    • Exécuter cat /dev/tcp/... dans un autre shell produit également une erreur
  • Selon le manuel de Bash, dans /dev/tcp/host/port, si host est un nom d’hôte valide ou une adresse Internet et port un numéro de port entier ou un nom de service, Bash tente d’ouvrir un socket TCP
  • Bash effectue la résolution DNS et le connect(2), et exec 3<> relie le socket au descripteur de fichier 3, ce qui permet la lecture et l’écriture

Un outil de vérification temporaire, pas un remplaçant d’un client HTTP

  • Cette approche n’est pas un véritable client HTTP et ne gère donc ni les redirections, ni les réponses chunked, ni la compression, ni les nouvelles tentatives, ni TLS
  • L’en-tête Connection: close est important
    • Sans lui, le serveur peut garder la connexion ouverte selon le comportement par défaut de HTTP/1.1
    • Dans ce cas, cat <&3 peut ne jamais se terminer en attendant un EOF
  • L’encapsuler avec quelque chose comme timeout 6 bash -c '...' permet aussi de se prémunir contre les cas où la connexion ne se ferme pas
  • Comme /dev/tcp ouvre un socket brut, cela ne vaut que pour HTTP en clair ; pour https, il faut openssl s_client
  • Ce n’est pas une fonctionnalité POSIX mais une fonctionnalité de Bash ; on ne peut donc pas l’utiliser avec dash, qui est le /bin/sh de Debian, ni avec zsh, et il faut appeler bash directement
  • C’est une option de compilation activée avec --enable-net-redirections lors de la construction de Bash
  • En résumé, ce n’est pas vraiment un outil généraliste pour remplacer curl, mais plutôt quelque chose d’adapté à une vérification rapide de la connectivité dans de petits conteneurs où il est impossible d’ajouter des installations

1 commentaires

 
GN⁺ 4 시간 전
Avis sur Hacker News
  • Enfant, à la fin des années 90, j’ai été bouleversé en découvrant qu’on pouvait se connecter avec telnet aux ports 80, 25 et 110 et parler directement au serveur
    On pouvait taper soi-même une simple requête GET / HTTP/1.1, ou envoyer un mail sur le port 25 avec HELO, mail-from, mail-to, et récupérer la liste de la boîte aux lettres ainsi que les messages individuels via POP3
    Cette expérience a marqué le début de la prise de conscience qu’« il n’y a pas de magie » : chaque partie d’un ordinateur a été fabriquée par des humains, et avec des efforts on peut en comprendre le fonctionnement jusqu’à un certain niveau
    À l’avenir, la plupart des choses seront sans doute confiées à des agents, mais il restera probablement dans divers systèmes des interstices intéressants pour ceux qui veulent apprendre le fonctionnement réel sans les filtres des modèles et des garde-fous

    • À l’époque, sans DKIM/SPF et avec une authentification SMTP très laxiste, on pouvait envoyer des mails avec une adresse comme jacques.chirac@elysee.fr et se donner des airs de hacker devant ses amis
    • Non seulement il n’y avait pas encore DKIM ni SPF, mais la plupart des serveurs SMTP étaient des open relays qui acceptaient les mails de n’importe qui vers n’importe qui
    • Au fond, ce ne sont que des fichiers texte
      C’était une pile de sigles posée sur différentes manières de créer, envoyer et lire des fichiers texte structurés
      Le jour où j’ai compris que même une base de données était, elle aussi, un fichier texte, j’ai dû m’asseoir un moment
    • Au siècle dernier, au bureau, pour lire et envoyer ses mails personnels, on se connectait séparément en telnet à POP3 et SMTP
    • Avec HTTP/2, on ne peut plus faire ça de cette façon, mais heureusement presque tous les serveurs parlent encore aussi HTTP/1
      TLS ne fonctionne pas non plus avec telnet, et beaucoup de serveurs ne renvoient qu’une redirection pour les requêtes HTTP
      En remplaçant telnet par openssl s_client, on peut tout de même tunneliser du texte dans TLS, même si ça donne un peu l’impression d’un détournement
      C’est aussi dommage que beaucoup de protocoles modernes préfèrent un encodage binaire, ce qui les rend difficiles à manipuler au niveau filaire sans outils dédiés
      Malgré tout, il y aura sans doute encore à l’avenir des gens pour creuser ce genre de choses, et les vieilles techniques comme allumer un feu avec un bâton ou cuire des briques d’argile sont amusantes et parfois réellement utiles
      L’IA facilite même les expérimentations : sans aller fouiller les RFC, on peut demander à un LLM et apprendre, par exemple, la plupart des commandes IMAP courantes
  • zsh dispose, en plus du /dev/tcp de Bash, des modules zsh/net/tcp et 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....

  • Dans Plan 9, il y avait un vrai système de fichiers synthétique /net, qui permettait ce genre d’opérations et bien plus depuis n’importe quel programme
    On pouvait même monter le /net d’une autre machine via le protocole 9P et l’utiliser comme une sorte de VPN improvisé, et on peut expérimenter ça sous Linux avec 9front
    On retrouve aussi des traces du /net à la Plan 9 dans la bibliothèque Go, ce qui ressemble à l’héritage de Rob Pike

  • Ça fonctionne très bien avec example.com
    On ouvre avec exec 3<>/dev/tcp/example.com/80, on envoie printf 'GET / HTTP/1.1\r\nHost: example.com\r\nConnection: close\r\n\r\n' >&3, puis cat <&3 renvoie HTTP/1.1 200 OK
    Aujourd’hui, il reste si peu de domaines qui n’imposent pas HTTPS qu’on finit souvent par utiliser example.com pour ce genre de test

    • example.com est aussi utile quand un portail captif de Wi‑Fi public se dérègle
      Dans le navigateur, aller sur http://example.com provoque une redirection vers la page du portail captif, ce qui permet de refaire la procédure d’accès à Internet
    • On peut aussi mettre de vrais retours à la ligne dans le printf, ça fonctionne
      Les \r devraient être là en théorie, mais même sans eux ça marche
  • On peut plaisanter en disant que, pour parler à l’ordinateur d’un ami, tout le monde utilise bash -i >& /dev/tcp/IP/PORT 0>&1

  • Bash ne « parle » pas HTTP ; il permet d’ouvrir des sockets TCP
    Ce qu’on fait ici, c’est parler HTTP directement ; c’est acceptable pour des tests ou du débogage, et c’est amusant à faire à la main, mais utiliser ce faux client HTTP dans un environnement réellement non supervisé finira par se retourner contre vous
    Ce code-jouet peut casser parce qu’il ne parse pas correctement HTTP
    Bien sûr, on peut écrire un client HTTP/1.1 complet en Bash, et on peut aussi faire un serveur HTTP en Bash pur : https://github.com/bahamas10/bash-web-server
    Une option moins folle, c’est généralement nc, et c’est le plus souvent le choix le plus sensé

    • L’expression « serveur HTTP complet en Bash pur » est, à strictement parler, inexacte
      Bash ne peut pas écouter des sockets TCP/UDP pour accepter des connexions entrantes
      Le projet bash-web-server compile un écouteur de sockets en C, puis fournit cette fonctionnalité en le chargeant dynamiquement comme module « intégré » à l’exécution
      [0] https://github.com/bahamas10/bash-web-server/tree/main/loada...
    • La remarque est juste, et la formulation dans l’article était excessive ; je vais la corriger pour être plus précis
      nc ou un outil similaire de la famille netcat serait un meilleur choix, mais l’image utilisée à ce moment-là ne contenait pas ce genre d’outil
    • Ce n’est pas si fou que ça
      On tape des requêtes HTTP à la main depuis avant HTTP/1.1 et l’arrivée de l’en-tête Host obligatoire
      C’est une folie pour un usage sérieux, et implémenter un serveur web en Bash l’est tout autant, mais pour des tests rapides c’est plutôt bien
    • Quelqu’un a même fait un serveur Minecraft en Bash pur
      https://sdomi.pl/weblog/15-witchcraft-minecraft-server-in-ba...
    • Il existe aussi un framework façon Rails pour Bash : https://github.com/jneen/balls
  • J’ai appris ça en voyant l’équipe Bauhinia l’utiliser pour résoudre un défi CTF
    C’était un CTF en plusieurs étapes, et au début on obtenait un shell system via une chaîne ROP, mais c’était en pratique un environnement carcéral où l’on ne pouvait exécuter quasiment rien d’autre que Bash
    On n’avait guère que read et cat, donc on a utilisé cat /dev/tcp, puis on l’a redirigé vers un terminal virtuel ; en lisant son contenu, on a récupéré l’URL d’un système interne et trouvé le flag

  • En vérifiant la connectivité entre conteneurs sur un réseau Docker interne, j’ai découvert cette méthode parce que l’image ne contenait ni curl ni wget
    Ce qui m’a surpris, c’est que Bash dispose de /dev/tcp, ce qui permet de bricoler quelque chose qui ressemble à une requête HTTP avec un peu de magie shell
    Par exemple, on peut ouvrir avec exec 3<>/dev/tcp/service/8642, envoyer printf 'GET /health HTTP/1.1\r\nHost: service\r\nConnection: close\r\n\r\n' >&3, puis faire cat <&3
    Ici, service est le nom d’hôte ciblé et 8642 est le port auquel on essaie de parler en HTTP

    • C’est sympa, mais je me demande s’il y a un inconvénient à simplement utiliser une image compatible avec curl
      Je n’en vois aucun, et j’estime que c’est presque indispensable, même dans une image de production
  • Autrefois, cette fonctionnalité ne marchait pas sur Debian et les distributions dérivées de Debian, parce que l’accès TCP via ce fichier virtuel était désactivé par défaut
    Si j’ai bien compris, la position a changé en 2009 et la fonctionnalité a été activée ; la discussion et les liens figurent dans le bug #146464
    <https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=146464#37>
    Il existe aussi d’autres moyens d’accéder directement aux fonctions réseau depuis des outils shell, notamment curl, wget, les commandes HEAD et GET de Perl, netcat/nc, socat, telnet, etc.

  • Je me souviens qu’adolescent, j’envoyais des messages flippants avec echo vers le /dev/ptty d’autres personnes pour les surprendre
    Les messages que j’envoyais apparaissaient comme par magie dans leur terminal ouvert
    Je ne sais toujours pas pourquoi, en salle informatique, on utilisait des comptes différents sur chaque client sans les verrouiller ; c’était peut-être une limite des VAX à l’époque