SSLH : ssh et https sur le même port
Le problème de 2007, dans les boîtes à pare-feux nerveux: un seul port sortant, le 443, tout le reste est coupé. On veut son shell ssh et son https et le firewall ne discute pas. La réponse s’appelle sslh (v1.0, de Yves Rutschle): un petit daemon qui écoute le 443 et délègue selon le premier paquet qui arrive: à sshd si le client parle protocole SSH, à httpsd sinon.
Le mécanisme
client --443--> sslh --détection--> sshd:22
\--> https:443
Le protocole SSH annonce poliment son identité: le client envoie une bannière SSH-2.0-... en clair dès l’ouverture. Le protocole SSL/TLS commence par un ClientHello dont le premier octet n’est pas un ‘S’. Deux sons de cloche, deux destinations:
# sslh.cfg, l'esprit de l'époque
listen: { host: "0.0.0.0", port: "443" }
protocols:
- { name: ssh, service: "localhost:22" }
- { name: ssl, service: "localhost:444" }
Il se lance en root pour binder le 443, puis setuid nobody (comme le font les bons daemons). Le ping ssh passe, le https passe, le firewall ne voit qu’un flux 443 bien élevé.
Ce que ça a permis
En pratique: ssh depuis les cafés et les immeubles de bureaux coupés du monde, le https d’administration sur le même port et un portknocking en moins (voir l’article de novembre qui arrive dans la série). Café du coin, réseau étranglé, un seul port ouvert et pourtant mon shell passait: le 443 devenait bavard et moi souriant. Pour le sysadmin itinérant de 2007, c’était la liberté et ça tenait en quelques centaines de lignes de C.
[edit: 2026] Pourquoi on ne le met plus en prod
Honnêteté rétrospective, les raisons dans l’ordre de gravité:
- Le fingerprinting tue l’obscurité. Le “port caché” est découvrable par comportement: un scanner qui parle SSH d’abord obtient la bannière
SSH-2.0, un ClientHello suivant obtient un TLS. sslh ne cache pas le service, il décale sa détection d’un paquet et il existe des nmap scripts qui font exactement ça. - La philosophie de l’époque moderne est l’inverse. Depuis WireGuard , le modèle qui gagne est “silencieux, crypto-first, ports standards”: un non-pair obtient le silence absolu, pas un multiplexeur qui lui parle. Cacher ssh derrière un 443 partagé, c’est re-créer la tour de magie que WireGuard a démoli.
- La surface admin. Un daemon C supplémentaire entre le monde et sshd, avec fork de connexions et parsing de bannières: un SPOF et une CVE de plus, sur le chemin de la donnée la plus sensible de la machine.
- Le TLS moderne ne se multiplexe pas comme ça. ALPN, SNI, HTTP/2 et les terminaisons TLS dans les LB/CDN supposent un 443 qui parle TLS proprement; greffer un ssh dans le flux brouille les probes, les logs et le mirroring.
- Le port n’est plus rare. IPv6, les IP par service, les SRV records: la contrainte “un seul port sortant” qui justifiait sslh a disparu avec les pare-feux qui l’imposaient.
Et il faut raconter la mort du tour de magie: le DPI
Parce qu’il reste une dernière illusion à démonter, celle des paillasses modernes qui se disent “le DPI n’osera pas”: faire transiter ssh, https et un VPN-SSL dans le même 443 et espérer que le pare-feu ne capte rien. Les pare-feux ont arrêté de regarder les ports il y a longtemps. Un DPI moderne (les boîtes Palo Alto, Fortinet, Cisco et copines) classifie le trafic par grammaire, pas par numéro de port et le 443 ne lui apprend rien qu’il ne sache déjà.
Faire passer du ssh en clair dans le 443? Le premier paquet du client commence par SSH-2.0-, la classification tombe avant que vous ayez tapé votre première commande et le blocage tombe deux lignes de signature plus loin. Se rendre plus malin en enveloppant le ssh dans du TLS? Le ClientHello a une empreinte (les familles JA3/JA4 ont fait la fortune des équipes de détection), un ssh-wrapped-in-TLS n’a pas de SNI crédible, son certificat est auto-signé ou exotique et surtout la forme du trafic le dénonce: le protocole binaire de ssh a des tailles d’enregistrement et un padding caractéristiques et une session interactive envoie des paquets de la taille d’une frappe de clavier, à la cadence d’un humain, ce qui est le signal le plus reconnaissable du monde pour une machine qui cherche précisément ça. Le SSL-VPN OpenVPN en TCP/443 a son handshake P_CONTROL, SSTP joue au déguisement TLS mais son SNI et son enchaînement HTTP le trahissent et les VPN commerciaux ont chacun leur signature dans les bases du vendeur, gentiment maintenues à jour pour vous.
Autrement dit: espérer faire passer ssh, https et un VPN-SSL dans un même 443 sous le nez d’un firewall avec DPI, c’est espérer que le vigile ne connaisse pas les trois langues qu’il entend. Il les connaît. Il paie un abonnement pour ça. Le 443 n’est un couloir discret que pour du vrai TLS bien formé et encore: il reste le trafic shape à assumer. Ce que ce multiplexage a jamais caché, c’est le port. Jamais la langue.
Le chapitre n’est d’ailleurs pas clos et il serait malhonnête de faire comme si: la dissimulation a depuis changé de métier. AmneziaWG ajoute au WireGuard original les junk packets, les en-têtes de message personnalisés et le bourrage de handshake qui effacent sa signature de forme; udp2raw déguise l’UDP en TCP forgé, wstunnel emballe le tout dans un WebSocket sous vrai TLS avec un vrai SNI et Xray REALITY emprunte carrément le handshake TLS d’un vrai site pour porter du trafic quelconque. La course continue, le DPI répond par le probing actif et la détection statistique, la dissimulation répond en recopiant le vrai monde. Ces outils ont un emploi légitime (les réseaux hostiles, la censure), mais notons le retournement délicieux: pour survivre au DPI, il faut désormais ressembler au HTTPS que sslh prétendait cacher. La tour de magie a changé de propriétaire, pas de principe.
sslh existe toujours (v2.x et ses cas d’usage restent réels: edge boxes piégées derrière un 443 unique, serveurs de démonstration, tarpits). Ce qui est mort, c’est l’idée de le mettre entre internet et sshd d’une flotte en prod: le silence cryptographique a gagné le premier débat et le DPI a gagné le second.