Tailworm : 11 failles non corrigées dans Tailscale, dont un accès root via Tailscale SSH
Ou : Comment un réseau « zéro confiance » faisait entièrement confiance à son propre démon
Le 25 octobre 2026, Erica Windisch, de Cyberdione Labs, a publié Tailworm : onze failles dans le client Tailscale 1.104.2 et dans headscale 0.29.5, le serveur de coordination libre qu’utilisent ceux qui hébergent eux-mêmes leur réseau Tailscale1. Six sont jugées de gravité élevée. La pire touche Tailscale SSH, le serveur SSH intégré à l’agent : quand un utilisateur transférait un socket Unix, l’agent ouvrait la connexion avec les droits root. Une machine compromise du réseau privé, le « tailnet », peut alors devenir root sur les serveurs que la politique d’accès lui ouvre. La chercheuse a envoyé son rapport à Tailscale la veille de le publier. Le 27 octobre, Tailscale confirme des failles sérieuses, a fusionné des correctifs et vise une version corrigée dans la journée. Pas encore de version, pas de CVE.
Le rapport s’ouvre sur une seule phrase, en haut de la page : « we have zero-trust in our infrastructure ». Le jeu de mots est cruel parce qu’il est exact. Tailscale se vend comme un accès « Zero Trust » fondé sur l’identité : aucune machine n’en croit une autre sur la foi de son adresse, chaque connexion passe par une identité et une politique. Il restait pourtant quelqu’un à qui tout le monde faisait confiance : l’agent tailscaled lui-même, qui tourne en root.
Faille Tailscale SSH : comment une machine compromise devient root sur les autres
Ce que fait Tailscale SSH, et avec quels droits
Tailscale construit un réseau maillé entre les machines, chiffré par WireGuard. Tailscale SSH ajoute un serveur SSH dans l’agent : plus de clés à distribuer, l’identité du tailnet sert d’authentification, et la politique d’accès dit qui ouvre une session, sous quel compte local, sur quelle machine2. Sur Linux, l’agent tourne en root, ne serait-ce que pour créer son interface réseau. Les sessions, elles, s’exécutent sous le compte de l’utilisateur.
Le transfert de sockets Unix faisait exception. Avec ssh -L, on demande au serveur de se connecter à un socket de la machine distante et de relayer le trafic. OpenSSH fait cette connexion depuis un processus qui a déjà pris l’identité de l’utilisateur authentifié3. Tailscale SSH la faisait depuis tailscaled, donc en root. Il limitait bien les chemins au dossier personnel, à /tmp et à /run/user/<uid>, mais une fois le chemin accepté, c’était root qui composait le numéro.
C’est un standard d’hôtel. Le client demande à être mis en relation avec un poste. La réceptionniste vérifie que le poste est à son étage, puis appelle depuis la ligne du directeur, à laquelle tous les postes répondent, coffre compris. Dans le laboratoire de la chercheuse, le coffre était un démon Docker qui écoutait dans un dossier permis : résultat, uid=0(root) et le contenu de /etc/shadow, sans sudo ni groupe docker1.
Le correctif de juin, et la course d’octobre
Ce transfert avait déjà mené au socket Docker. Le 3 juin 2026, le bulletin TS-2026-004 décrivait la même porte : la vérification comparait le texte du chemin demandé à une liste, et un lien symbolique posé dans un dossier permis menait où il voulait4. La correction, livrée dans la version 1.98.9 : résoudre les liens, puis vérifier la destination.
Tailworm montre la limite de cette correction. Vérifier, puis utiliser, ce sont deux instants. Entre la résolution du lien et la connexion, l’utilisateur peut faire pointer le lien ailleurs, et la chercheuse le fait basculer environ 170 000 fois par seconde. Il suffit qu’une bascule tombe au bon moment. C’est la course que les anglophones appellent TOCTOU, time of check, time of use, et elle rend la liste des dossiers permis décorative.
Pour voir la taille de la fenêtre, j’ai reproduit le motif en Go, avec des fichiers à moi et sans aucun privilège : un dossier permis, un fichier public dedans, un fichier secret à côté, et un lien qu’une goroutine fait basculer de l’un à l’autre par renommage atomique. La fonction qui vérifie :
| |
Et une exécution de 200 000 essais, avec en seconde ligne la même lecture par os.Root (Go 1.27.3, Linux, Ryzen 7 5800H) :
| |
Sur cinq exécutions, la fonction a laissé lire le secret entre 16 397 et 17 187 fois, environ 8 % des essais, alors que chaque vérification était juste au moment où elle était faite. os.Root, arrivé avec Go 1.24 en février 20255, résout le chemin à partir d’un descripteur ouvert sur le dossier et refuse tout lien qui en sort, au moment même de l’ouverture : la vérification et l’usage ne font plus qu’un.
os.Root n’ouvre pas de socket, en revanche. Le correctif de Tailscale, fusionné le 26 octobre, suit le modèle d’OpenSSH : le transfert passe par un processus enfant qui porte l’identité de l’utilisateur connecté, et ce sont les permissions du fichier, vérifiées par le noyau, qui font la frontière6. Plus besoin de deviner où mène un chemin quand on n’a pas plus de droits que celui qui le demande.
De la machine compromise au nœud pirate
Les autres constats sur SSH prolongent le premier. Avec ssh -R, un utilisateur peut remplacer un socket appartenant à root par le sien et se faire passer pour le service. En laboratoire, la chaîne est allée jusqu’à capturer une clé d’inscription en route vers l’API locale de tailscaled, puis à faire entrer une machine pirate dans le tailnet sous l’identité de la victime. Il faut pour cela que Tailnet Lock, qui fait signer chaque nouveau nœud par des machines de confiance, soit désactivé, ce qui est le réglage par défaut7. Avec Tailnet Lock, les secrets nécessaires ont été capturés, mais la chercheuse n’a pas exécuté la dernière étape.
Le nom du rapport vient de là : une machine compromise, un accès SSH accordé par la politique, et la propagation peut s’automatiser vers chaque machine autorisée, sans mot de passe ni clé volée, en moins d’une seconde par saut. Le point de départ reste une première machine prise, par une application web vulnérable, un paquet piégé, un hameçonnage ou des identifiants volés. Tout le monde n’accepte pas le mot « ver », et personne d’autre n’a encore reproduit publiquement ces résultats8.
Les onze constats, regroupés par composant, avec l’état que donne le rapport au 27 octobre :
| Composant | Constats | Gravité la plus haute | État au 27 octobre |
|---|---|---|---|
| Tailscale SSH (transfert, course, mémoire) | 1, 2, 3, 5 | Élevée | Corrigés dans le dépôt, dans aucune version |
| Connecteurs d’applications (conn25, expérimental) | 4 | Élevée | Non corrigé |
| headscale et relais DERP | 6 | Moyenne à élevée | Côté client corrigé dans le dépôt, côté headscale en cours |
| Serve et Funnel | 7 à 11 | Élevée (constat 7) | 7 et 11 corrigés dans le dépôt, 8 à 10 non ou affaire de configuration |
Signalé un samedi, publié le dimanche
La chercheuse a envoyé le rapport et ses preuves de concept au contact sécurité de Tailscale le samedi, et publié le dimanche. Elle parle d’une divulgation signalée mais pas coordonnée8. Son argument tient en une ligne du rapport : les infrastructures sont vulnérables aujourd’hui, et plus vite on le sait, plus vite on corrige. Les preuves de concept, elles, n’ont pas été publiées.
L’usage le plus courant est inverse. Project Zero, l’équipe de Google, laisse 90 jours à l’éditeur, puis publie les détails 30 jours après le correctif9. Tailscale a eu un jour, et quelques commentateurs ont jugé la méthode irresponsable. Il faut reconnaître au calendrier une efficacité certaine : la page d’enquête de Tailscale est en ligne le 26 octobre, le correctif SSH fusionné le même jour, la version promise pour le 27. Rien n’accélère un correctif comme un rapport public un dimanche.
Tailscale, de son côté, décrit surtout des élévations de privilèges : un utilisateur déjà autorisé à se connecter par Tailscale SSH pourrait dépasser les droits du compte local prévu. L’entreprise vérifie encore quelles configurations sont touchées et ne recommande pas à tout le monde de couper Tailscale SSH10. Son vice-président de l’ingénierie, Mike Shaver, a ajouté sur Reddit qu’elle examinait déjà ces zones avec des spécialistes extérieurs8.
DevOps Dave : On a tout mis derrière Tailscale. Plus un seul port ouvert. Zéro confiance.
Security Sarah : Qui a le droit de faire du SSH vers le serveur de base de données ?
DevOps Dave : Tout le groupe des développeurs. C’est plus simple qu’une règle par personne.
Security Sarah : Donc un paquet piégé sur un seul portable ouvre la base.
DevOps Dave : Seulement depuis le tailnet !
Security Sarah : Le portable est dans le tailnet, Dave. C’est pour ça qu’on l’y a mis.
Que régler en attendant la version corrigée
D’abord, installer la version corrigée dès sa sortie : Tailscale promet un bulletin avec les versions et les configurations concernées. D’ici là, si le transfert ne sert à rien, une variable d’environnement le coupe dans l’agent, sans couper Tailscale SSH10 :
| |
Je n’utilise pas Tailscale et je n’ai pas exécuté ces deux lignes. J’ai lu le code de la version 1.104.2 : la variable y est déclarée et vérifiée avant chacun des quatre transferts, TCP et Unix, dans les deux sens, et le fichier de service fourni par Tailscale charge bien /etc/default/tailscaled.
Le reste, d’après le rapport et la page de Tailscale :
- Tailscale SSH inutile :
sudo tailscale set --ssh=false, après s’être assuré d’un autre accès à la machine. - Règles d’accès : une source vers une destination, plutôt qu’un groupe ou un tag entier, et jamais vers
rootni un compte qui asudosans mot de passe. Le mode de vérification renforcé (check) ne protège pas le transfert : il ne contrôle que l’ouverture de session. - headscale : en 0.29.5, la politique ne sait pas interdire le transfert. La variable de l’agent est le seul interrupteur ; à défaut, chaque accès SSH accordé vaut un accès root.
- Connecteurs et Serve : retirer la capacité de connecteur d’applications là où elle ne sert pas, et tout
tailscale servequi pointe vers un socket Unix. - Machine compromise : la supprimer côté serveur de coordination. Réinstaller le système ne suffit pas, l’état copié reste valable.
Le rapport ne dit rien d’autre que ce que dit le principe du Zero Trust : ne faire confiance à aucune machine sur la foi de sa position. Il oublie juste une exception, celle qui applique la règle. C’est la même erreur que faire confiance à localhost, un étage plus bas : la frontière n’est pas l’endroit d’où vient la demande, c’est l’identité sous laquelle on l’exécute. Cet article décrit la situation au 27 octobre 2026, en début d’après-midi, heure de Paris ; la version corrigée devrait suivre de près. Le bulletin dira combien des onze constats Tailscale retient.
Le rapport Tailworm de Cyberdione Labs : tests en laboratoire du 22 au 24 octobre 2026 sur Tailscale 1.104.2 et headscale 0.29.5, constats numérotés de 1 à 11, état des correctifs mis à jour le 27 octobre. ↩︎ ↩︎
Documentation de Tailscale SSH. ↩︎
Le fichier README.privsep d’OpenSSH : après l’authentification, le processus de session abandonne les droits root pour ceux de l’utilisateur authentifié. ↩︎
Les bulletins de sécurité de Tailscale, TS-2026-004. La même année, TS-2026-006 et TS-2026-009 décrivaient deux autres chemins vers une session root par Tailscale SSH, l’un par l’identifiant numérique
0, l’autre par un nom d’utilisateur commençant par un tiret, lu comme une option pargetent. Les trois sont corrigés dans la version 1.98.9. Tailworm est donc au moins le quatrième chemin de l’année. ↩︎Notes de version de Go 1.24, publiées en février 2025.
Root.ReadFile, utilisé dans mon test, est venu avec Go 1.25. ↩︎La PR #21942 du dépôt tailscale/tailscale, fusionnée le 26 octobre 2026 : chaque connexion SSH reçoit un processus moniteur, et les sockets transférés sont ouverts ou créés par des processus enfants qui portent l’identité de l’utilisateur. Les permissions du noyau remplacent la vérification par résolution des liens symboliques. ↩︎
Documentation de Tailnet Lock. ↩︎
The Hacker News, article du 25 octobre 2026 mis à jour le 27 octobre. ↩︎ ↩︎ ↩︎
La politique de divulgation de Project Zero : 90 jours pour corriger, puis 30 jours après la sortie du correctif avant la publication des détails. ↩︎
La page d’enquête de Tailscale sur Tailscale SSH, publiée le 26 octobre 2026. ↩︎ ↩︎