Québec, Canada

403-1381 1re Avenue

+1 581.849.27.96

bdgouthiere@gmail.com

Alpine Linux et musl : un mot de passe trop long suffisait pour entrer en SSH

Ou : Quand « pas de mot de passe » et « erreur » s’écrivent avec le même caractère

Le 5 novembre 2026, OpenSSH a publié la version 10.6p2 pour corriger une faille que son découvreur, Özgün Kültekin, de la société Wakefield, a baptisée « *sshole »1. Sur Alpine Linux et les autres systèmes bâtis sur la bibliothèque C musl, quand le serveur sshd tourne sans PAM, n’importe quel mot de passe de 257 à 1 024 caractères ouvrait une session sur un compte qui n’en a pas : un compte dont le champ de /etc/shadow vaut *, comme ceux qu’on ne connecte que par clé2. Enregistrée sous CVE-2026-123158, la faille est exploitée : GreyNoise a observé une première intrusion réussie le 7 novembre, et l’agence américaine CISA l’a inscrite le 9 novembre à son catalogue des vulnérabilités exploitées3. Alpine publie des paquets corrigés pour toutes ses branches, de la 3.16 à edge.

Le surnom, peu diplomatique, joue sur l’astérisque. Dans /etc/shadow, * veut dire « personne n’entre ici avec un mot de passe ». Dans musl, * est ce que renvoie la fonction crypt() quand elle renonce. sshd comparait les deux chaînes. Elles étaient identiques.

Faille OpenSSH sur Alpine : pourquoi musl change le sens d’un compte sans mot de passe

Un astérisque, deux sens

Sans PAM, sshd vérifie un mot de passe à l’ancienne : il le passe à crypt() avec le sel du compte, puis compare le résultat au condensat stocké dans /etc/shadow. Quand le champ ne contient pas de vrai condensat, comme *, sshd calcule quand même, avec le sel du premier compte qui en a un, pour que le temps de réponse ne trahisse rien4. Il faut donc qu’un autre compte de la machine ait un vrai mot de passe, haché en SHA-256 ou en SHA-512.

C’est là qu’intervient musl. Ses fonctions SHA-256 et SHA-512 refusent les mots de passe de plus de 256 octets : le coût du calcul croît avec le carré de la longueur, et un vérificateur sans limite se laisserait épuiser. En cas d’échec, musl renvoie *5. De son côté, sshd écarte d’office tout mot de passe de plus de 1 024 octets. Entre les deux bornes, crypt() renvoie *, le compte contient *, et la comparaison réussit.

C’est un gardien d’immeuble qui compare des étiquettes. Sur la porte d’un appartement vide, une étiquette « X » : personne n’habite ici. Le serrurier, quand on lui demande une clé trop longue à tailler, rend lui aussi une étiquette « X ». Le gardien pose les deux côte à côte, constate qu’elles sont pareilles, et ouvre.

Tout tient à la valeur que crypt() renvoie quand elle échoue :

ImplémentationValeur renvoyée en cas d’échecÉgale à un champ * ?
Norme POSIXun pointeur nulNon
libxcrypt*0 ou *1, jamais égale au réglage reçuNon
musl* (ou x si le réglage est *)Oui

Le même code, deux niveaux de sécurité

Le code de sshd est le même partout. Lié à libxcrypt, qui fournit crypt() à Debian notamment, il refuse la connexion6. Lié à musl sur Alpine, il l’accepte. La sécurité d’un programme C ne s’arrête pas à son code source : elle dépend aussi des conventions de la bibliothèque avec laquelle on le lie, jusqu’au contenu d’une valeur de retour. La fiche CVE range d’ailleurs la faille dans la CWE-394, la catégorie des codes de retour inattendus7.

Chacun renvoie la balle, poliment. OpenSSH situe la cause hors de sshd, mais se dit bien placé pour l’éviter1. Rich Felker, qui maintient musl, y voit « une utilisation erronée par l’appelant », pas un bogue de musl5. Les deux corrigent quand même. Le correctif d’OpenSSH, écrit par Damien Miller, refuse toute valeur stockée de moins de quatre caractères, plus courte que n’importe quel vrai condensat8. Felker propose de passer musl aux jetons d’échec *0 et *1, comme les autres implémentations. Au 11 novembre 2026, ce changement n’est pas encore dans le dépôt de musl.

« Verrouillé » ne veut pas dire la même chose partout

La presse a parlé de comptes verrouillés. Le mot est trompeur. Selon la page de manuel shadow(5), un champ qui commence par ! signale un mot de passe verrouillé, et toute valeur que crypt() ne peut pas produire, * ou !, interdit le mot de passe Unix sans fermer les autres moyens d’entrer. passwd -l ajoute justement un ! en tête, et son manuel prévient qu’une clé SSH peut encore servir9.

Sur Alpine, sans PAM, sshd va plus loin : un compte en ! n’entre ni par mot de passe ni par clé. C’est pour rendre les clés utilisables qu’Alpine a remplacé, vers la 3.16, le ! de root par un *10. Résultat : les comptes en !, vraiment verrouillés, ne sont pas touchés. Les comptes en *, ceux qui n’ont « pas de mot de passe », le sont.

Qui est exposé, d’après les sources

Il faut réunir toutes les conditions suivantes210 :

  • un sshd lié à musl : Alpine, ou un système embarqué (OpenWrt, Buildroot, Yocto) qui associe musl et le serveur d’OpenSSH ;
  • pas de PAM : le paquet openssh-server d’Alpine, ou openssh-server-pam réglé sur UsePAM no ;
  • PasswordAuthentication yes, la valeur par défaut ;
  • un compte cible en * : compte à clé seule, ou utilisateur d’annuaire sans entrée dans /etc/shadow ;
  • un autre compte avec un vrai mot de passe en SHA-256 ou SHA-512, le premier dans l’ordre de /etc/passwd.

root reste protégé par le réglage par défaut PermitRootLogin prohibit-password, qui lui refuse les mots de passe11. Sauf si un autre compte exposé peut devenir root : c’est le cas des images cloud officielles d’Alpine, de la 3.18.11 à la 3.24.3, dont l’utilisateur alpine, en *, dispose de doas sans mot de passe. Elles deviennent vulnérables dès qu’un vrai mot de passe apparaît sur la machine2.

Côté Docker, un conteneur n’est concerné que s’il fait tourner sshd. Une image -alpine qui ne lance qu’une base de données, comme dans les tests d’intégration avec Testcontainers, ne l’est pas ; une image qui embarque sshd pour un accès SFTP l’est, si le reste est réuni. Les systèmes à glibc avec PAM, Dropbear et les produits Red Hat sont hors de cause710.

DevOps Dave : Rien à craindre. Mon compte de déploiement n’a pas de mot de passe, rien qu’une clé. Seul l’administrateur en a un, pour la console.

Security Sarah : Donc un astérisque dans /etc/shadow, et un vrai condensat juste à côté.

DevOps Dave : Et mon client se connecte avec -o PasswordAuthentication=no.

Security Sarah : Ton client n’envoie pas de mot de passe. Ton serveur en accepte toujours, et sur Alpine il prenait ceux de 300 caractères.

DevOps Dave : Personne ne tape un mot de passe de 300 caractères.

Security Sarah : Personne, non. Un script, si.

Vérifier, corriger, couper les mots de passe

D’abord, la version installée, à comparer aux paquets corrigés d’Alpine2 :

1
2
# Paquets OpenSSH installés et leur version
apk list --installed 'openssh*'
Brancheopenssh corrigéBrancheopenssh corrigé
edge10.6_p2-r03.209.7_p1-r6
3.2410.3_p1-r23.199.6_p1-r3
3.2310.2_p1-r13.189.3_p2-r4
3.2210.0_p1-r113.179.1_p1-r7
3.219.9_p2-r13.169.0_p1-r6

Ensuite, la mise à jour, puis le redémarrage : le démon déjà lancé garde l’ancien code tant qu’on ne le relance pas.

1
2
apk upgrade -U -a
rc-service sshd restart

Pour un conteneur, on regarde s’il lance sshd, puis sa version. S’il en lance un, on reconstruit l’image sur une base à jour plutôt que de corriger le conteneur en place, qui perdrait le correctif au prochain déploiement.

1
2
3
# Un sshd parmi les processus du conteneur ? Si oui, sa version
docker top <conteneur>
docker exec <conteneur> apk list --installed 'openssh*'

Enfin, passer aux clés et couper les mots de passe, ce qui ferme la faille même sans mise à jour. On copie la clé tant que le mot de passe fonctionne encore, on vérifie qu’elle marche, et on garde une session ouverte pendant le changement :

1
2
3
4
5
6
7
8
9
# Sur le poste
ssh-keygen -t ed25519
ssh-copy-id -i ~/.ssh/id_ed25519.pub utilisateur@serveur

# Sur le serveur, en root
mkdir -p /etc/ssh/sshd_config.d
echo 'PasswordAuthentication no' > /etc/ssh/sshd_config.d/00-no-passwords.conf
sshd -t && rc-service sshd restart
sshd -T | grep -i passwordauthentication   # doit afficher « passwordauthentication no »

Alpine lit ce dossier par défaut depuis la 3.19 ; pour les versions antérieures, son avis donne la commande sed qui modifie sshd_config. La dernière ligne fait foi, puisqu’elle affiche la configuration réellement appliquée.

Pour savoir si quelqu’un est déjà entré, Alpine conseille de chercher les connexions par mot de passe réussies sur des comptes qui n’en ont pas, puis de passer en revue doas, les comptes récents et les fichiers authorized_keys, où une clé ajoutée en douce suffit à revenir plus tard :

1
grep 'Accepted password for' /var/log/messages

Si on ne peut pas exclure une connexion, la machine est à traiter comme compromise.

Fin octobre 2026, Tailscale SSH ouvrait des sockets en root au nom de l’utilisateur. Cette fois, il n’y a ni course ni débordement de mémoire : deux conventions raisonnables, chacune documentée, qui se rencontrent dans un strcmp. Cet article décrit la situation au 11 novembre 2026. En typographie, l’astérisque renvoie à une note en bas de page. Celui de /etc/shadow en avait une, et elle dépendait de la libc.



  1. Les notes de version d’OpenSSH 10.6p2, datées du 5 novembre 2026 : un contournement de sécurité pour les systèmes Linux qui utilisent musl sans PAM, signalé par Özgün Kültekin. L’annonce sur oss-security, le même jour, donne PasswordAuthentication no comme parade. ↩︎ ↩︎

  2. L’avis d’Alpine Linux du 5 novembre 2026 : branches 3.16 à 3.24 et edge touchées, versions corrigées par branche, comptes en * vulnérables et comptes en ! épargnés, root livré en root:* depuis la 3.16 environ, images cloud, commandes de correction et de détection. Selon l’avis, la mise à jour du paquet seule « does not replace the running daemon ». ↩︎ ↩︎ ↩︎ ↩︎

  3. La page de suivi de GreyNoise, mise à jour le 11 novembre 2026 : première exploitation réussie le 7 novembre à 0 h 46 UTC sur l’une des machines vulnérables que GreyNoise avait déployées, 26 chaînes d’exploitation réussies au total. Le catalogue des vulnérabilités exploitées de la CISA l’a ajoutée le 9 novembre et fixe aux agences fédérales américaines une échéance au 12 novembre. ↩︎

  4. Dans le code d’OpenSSH avant le correctif, xcrypt.c emprunte le sel du premier compte qui en a un ; le commentaire parle d’un calcul factice « for timing purposes ». La limite de 1 024 octets est dans auth-passwd.c, constante MAX_PASSWORD_LEN. ↩︎

  5. L’analyse de Rich Felker sur la liste de musl, le 6 novembre 2026 : coût quadratique de SHA-256 et SHA-512, jeton d’échec * (ou x si le réglage est *), conditions de la faille et projet de passer à *0 et *1. La limite de 256 octets se lit dans crypt_sha512.c, constante KEY_MAX. Le journal du dépôt de musl, consulté le 11 novembre 2026, ne contient aucune modification postérieure au 14 octobre. ↩︎ ↩︎

  6. Le manuel de crypt(3) de libxcrypt : en cas d’erreur, une chaîne invalide qui commence par * et ne vaut jamais le réglage reçu, choisie pour que les applications « fail closed ». Le même manuel rappelle que POSIX prévoit un pointeur nul. ↩︎

  7. La fiche CVE-2026-123158, publiée le 5 novembre 2026 par MITRE : sshd avant 10.6p2, Linux avec musl et sans PAM, score CVSS 3.1 de 8,1, CWE-394. La page de Red Hat déclare ses produits non touchés, car compilés avec glibc et PAM. ↩︎ ↩︎

  8. Le commit 4353fdc d’openssh-portable, du 4 novembre 2026, qui ajoute la constante MIN_PASSWORD_HASH_LEN, fixée à 4. ↩︎

  9. Les pages de manuel shadow(5) et passwd(1). La seconde conseille usermod --expiredate 1 pour désactiver vraiment un compte. ↩︎

  10. Le billet du chercheur : systèmes musl concernés, dont OpenWrt, Buildroot et Yocto, passage de root de ! à * dans Alpine 3.16 pour que les clés fonctionnent sans PAM, et systèmes épargnés, dont Dropbear et les distributions à glibc. ↩︎ ↩︎ ↩︎

  11. La page de manuel sshd_config(5) : PasswordAuthentication vaut yes par défaut, PermitRootLogin vaut prohibit-password. ↩︎