Un caractère, deux significations sans rapport, un contournement d’authentification
J'ai d'abord envoyé un mot de passe incorrect. Puis j'ai envoyé un mot de passe de 300 octets de long, vers un compte sans aucun mot de passe défini. sshd m'a laissé entrer.
Deux systèmes qui n’avaient jamais entendu parler l’un de l’autre ont rendu cela possible, en choisissant tous deux le même caractère pour signifier "il n’y a rien d’utilisable ici". /etc/shadow utilise depuis des décennies un simple astérisque pour marquer un compte qui ne devrait jamais accepter de mot de passe. La fonction crypt() de musl, lors de certains échecs internes, renvoie exactement ce même caractère au lieu de la valeur nulle que POSIX spécifie réellement. Aucun des deux choix n’est erroné en soi. Mais en les plaçant dans la même comparaison de chaînes, on peut faire croire à sshd qu’un mot de passe correspond alors que rien n’a jamais été réellement comparé.
Ce premier paragraphe est aussi la seule donnée que cet article ne se permettra pas d’exagérer : un unique mot de passe excessivement long, envoyé comme première et unique tentative d’une connexion fraîche contre le même compte, sans mot de passe incorrect envoyé auparavant, n’a pas authentifié, lors de la même exécution de laboratoire. Une séquence a fonctionné. L’autre non. C’est une seule donnée issue d’une seule exécution, pas une affirmation selon laquelle toute première tentative de mot de passe contre un compte vulnérable réussirait. La différence entre ces deux résultats, et pourquoi elle s’est produite, est traitée directement et honnêtement plus loin, y compris les parties qui restent véritablement non résolues.
Cet article parcourt d’abord le contexte : ce que fait réellement l’authentification SSH, ce que stocke /etc/shadow, ce qu’est PAM et pourquoi compiler sans lui compte ici, pourquoi musl n’est pas glibc, et ce que crypt() est censé faire en cas d’échec. Ce n’est qu’une fois cela posé que le mécanisme de cause racine, les preuves de laboratoire, le correctif et les questions ouvertes prennent réellement sens, sans demander au lecteur de rien accepter sur la foi.
Si vous administrez une machine Linux basée sur musl avec l’authentification SSH par mot de passe activée et sans PAM, la version courte est la suivante : vérifiez votre fichier shadow à la recherche d’un simple astérisque, vérifiez si autre chose sur la machine possède un hachage $5$ ou $6$, et appliquez le correctif. La version longue, incluant exactement pourquoi cela fonctionne et exactement ce qu’une exécution de laboratoire pratique a confirmé, et n’a pas confirmé, fait l’objet du reste de cet article.
Un délai de réaction d’un jour, et le détail de l’avis que la plupart des couvertures médiatiques omettent
OpenSSH 10.6p2 est sorti le 5 novembre 2026 avec un seul point de sécurité, et il est sorti vite. L'annonce de publication de Damien Miller lui-même indique que le correctif est sorti le jour suivant la découverte du problème par l'équipe OpenSSH, et avance que des cas récents de bugs similaires redécouverts de façon indépendante par des agents d'IA en quelques heures signifient désormais qu'un long embargo bénéficie surtout aux attaquants plutôt qu'aux défenseurs. C'est la justification déclarée de ce calendrier compressé, pas une supposition que cet article ferait au nom de l'équipe. [2]
Les notes de version d’OpenSSH décrivent que sshd évite désormais "un contournement d’authentification par mot de passe qui affecte certains systèmes Linux compilés et exécutés avec le support PAM désactivé" (PAM, Pluggable Authentication Modules, est la couche que la plupart des systèmes Linux utilisent pour décider si une connexion réussit ; quelques sections plus loin la traitent correctement), et attribuent le signalement à Ozgun Kultekin de Wakefield. [3] Le propre article technique détaillé du chercheur se trouve sur sshhole.com, intitulé "*sshole", et indique de façon indépendante la même fenêtre de 257 à 1024 octets et la même collision de caractères que cet article retrace intégralement plus loin. [4] Le pseudonyme GitHub du site, oz9un, est cohérent avec le nom crédité, bien que rien dans le dossier ne confirme explicitement ce lien ; cela n’est déterminant pour aucune affirmation technique ici, simplement digne d’être mentionné clairement plutôt qu’affirmé comme un fait.
L’avis propre d’Alpine Linux est la deuxième source primaire sur laquelle repose tout cet article. Il déclare précisément l’état du compte vulnérable : un champ shadow qui est littéralement "*", et note explicitement qu’un compte verrouillé, dont le champ est "!", n’est pas affecté. Il formule la précondition générale comme nécessitant au moins un autre compte local avec un véritable hachage de mot de passe sur la même machine, le format SHA-256/SHA-512 par défaut d’Alpine, montré dans le tableau juste après, indique qu’un seul tel compte suffit à rendre atteignable tout compte sans mot de passe, et décrit l’attaque elle-même comme ne nécessitant qu’une seule connexion et aucune nouvelle tentative, une expression à laquelle cet article revient directement plus loin. [5]
CVE-2026-123158 porte lui-même un score de base CVSS 3.1 de 8.1 (élevé), vecteur AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:H, et CWE-394, Unexpected Status Code or Return Value, ce qui est la classification correcte pour un bug qui, au fond, consiste à faire confiance à une valeur signalant une erreur sans la vérifier. L’enrichissement propre de la CISA sur la même fiche évalue séparément la maturité d’exploitation comme "poc" et l’automatisabilité comme "non". Aucune exploitation en conditions réelles n’a été signalée au moment de la rédaction. [1]
La plupart des couvertures médiatiques secondaires de ce CVE le résument à "un mot de passe excessivement long contourne SSH". L’avis lui-même est plus précis sur trois points que presque toute cette couverture omet : le compte doit être exactement dans l’état "*", pas "!" ; autre chose sur la même machine doit déjà posséder un hachage utilisable ; et la fenêtre d’octets est de 257 à 1024, pas simplement "long" en général. Le reste de cet article est construit exactement autour de ces trois détails, plus celui que l’avis formule avec assurance mais qu’une exécution de laboratoire pratique a des raisons de sonder plutôt que d’accepter sur la foi.
Ce qui se passe réellement quand vous tapez un mot de passe SSH
SSH existe pour permettre à une machine d'administrer une autre sur un réseau auquel aucune des deux parties n'a nécessairement à faire confiance, chiffré et, idéalement, avec les deux parties prouvant être qui elles prétendent être. Un client se connecte à sshd, le démon côté serveur ; les deux négocient d'abord un canal sécurisé, et ce n'est qu'une fois ce canal établi que l'authentification intervient, par clé publique, par mot de passe, ou par une autre méthode configurée, avant qu'un shell ou une commande ne s'exécute. L'authentification par mot de passe, la méthode concernée par tout ce CVE, est une étape précise au sein de cette poignée de main plus large, pas un mécanisme séparé ajouté à côté.
Sur un système Linux, les informations de compte vivent dans /etc/passwd, une ligne par compte : nom d'utilisateur, identifiant numérique d'utilisateur, répertoire personnel, shell. Le mot de passe lui-même, ou plus précisément un hachage de celui-ci, y vivait aussi, il y a des décennies. Il vit désormais dans un fichier séparé, /etc/shadow, lisible uniquement par root, spécifiquement pour que compromettre un compte utilisateur ordinaire ne livre pas aussi tous les hachages de mots de passe de la machine.
Ce champ shadow n’est cependant pas toujours un hachage. Il porte un petit nombre de valeurs spéciales que les administrateurs, les outils, et sshd lui-même doivent tous interpréter correctement, résumées ci-dessous.
Par conception, un compte marqué d’un simple astérisque ne devrait pouvoir s’authentifier par mot de passe sous aucune entrée que ce soit, parce que "*" n’est pas du tout un format de hachage. C’est une convention : ce compte existe, mais aucun mot de passe ne le débloquera jamais. C’est exactement sur cette convention que s’appuient les administrateurs pour les comptes à clé uniquement, les identités de service, et les images de base de conteneurs qui livrent un compte root dans lequel personne ne s’attend à se connecter par mot de passe. Toute la vulnérabilité que retrace cet article est un cas où cette convention a silencieusement cessé de tenir.
| Champ de mot de passe | Signification | Concerné |
|---|---|---|
$1$... / $2a$/$2b$/$2y$... / $5$... / $6$...crypt() fonctionne normalement ici et produit un hachage authentique à comparer. | Un véritable hachage de mot de passe (MD5, bcrypt, SHA-256 ou SHA-512) | Non |
! ou !...L'avis d'Alpine l'indique explicitement. | Compte verrouillé | Non |
*Dans les conditions exactes tracées dans cet article. | Aucun mot de passe utilisable défini (compte à clé uniquement) | Oui |
(chaîne vide)Traité par une branche distincte et antérieure de sys_auth_passwd(), avant le chemin concerné par ce CVE. | Aucun mot de passe requis | Non |
PAM, et pourquoi compiler sshd sans lui change tout
PAM, Pluggable Authentication Modules, est la couche que la plupart des distributions Linux utilisent pour décider si une tentative de connexion, SSH incluse, réussit réellement. Une application comme sshd n'implémente normalement pas elle-même la vérification du mot de passe ; elle le demande à PAM, et PAM exécute les modules que l'administrateur a configurés pour ce service, pam_unix pour les mots de passe locaux étant le cas courant, d'autres étant disponibles pour les services d'annuaire ou les schémas multifacteurs. Cette indirection est le comportement par défaut sur les distributions basées sur glibc comme Debian, Ubuntu, Fedora et RHEL : sur ces systèmes, sshd n'appelle jamais directement une fonction de hachage de mot de passe. [2]
Alpine Linux, et la plupart des builds embarquées basées sur musl (OpenWrt, Buildroot, Yocto), compilent OpenSSH avec --without-pam à la place. Sur ces systèmes, il n'existe aucune couche PAM à qui déléguer. sshd se replie sur son propre code de comparaison générique, dans auth-passwd.c, appelant crypt(3) directement contre l'entrée shadow propre du compte. Cette unique décision prise à la compilation est la bifurcation sur laquelle ce CVE vit entièrement d'un seul côté : un système avec PAM activé n'atteint jamais la comparaison vulnérable, non pas à cause d'une défense spécifique qui l'en empêcherait, ce chemin de code ne s'exécute simplement jamais sur cette build. [2]
Un détail supplémentaire à connaître avant les deux sections suivantes, car la section sur la cause racine en dépend directement : pour un nom d’utilisateur que sshd considère invalide, le chemin sans PAM exécute quand même une comparaison, contre un faux mot de passe mis en cache, afin que rejeter un compte inexistant prenne à peu près le même temps que rejeter un mot de passe incorrect sur un compte réel. C’est une défense temporelle délibérée et établie depuis longtemps contre l’énumération de noms d’utilisateur, et elle partage exactement le même code de sélection de sel qui dirige un compte "*" vers la mauvaise branche. "Code de sélection de sel" signifie ici simplement la logique qui décide contre quelle valeur de référence crypt() compare un mot de passe ; ce qu’est exactement cette valeur de référence, et comment elle se trompe, est repris par les sections sur le mécanisme plus bas.
Une première étape commune, sshd reçoit une requête d'authentification par mot de passe, puis deux branches : une build avec PAM activé qui délègue à la pile PAM et n'appelle jamais crypt() directement, et une build sans PAM qui exécute son propre code de comparaison, se terminant par une comparaison via crypt() contre le hachage stocké, marquée comme l'unique étape dangereuse.
sshd reçoit une requête d'authentification par mot de passesshd n'appelle jamais crypt() directement--without-pam : Alpine, builds embarquées basées sur muslauth-passwd.c exécute sa propre comparaisonAucune couche PAM n'existe à qui déléguercrypt() contre le hachage stockéLe chemin exact où vit ce CVEmusl, glibc, et pourquoi ce bug ne touche pas Debian
libc, la bibliothèque standard C, est la couche entre un programme et le noyau du système d'exploitation sous-jacent : allocation mémoire, gestion des chaînes, et, pour cette histoire, crypt(). Tout programme Linux écrit en C se lie à l'une d'elles. La plupart des distributions de bureau et de serveur utilisent glibc, la bibliothèque C de GNU. Alpine Linux utilise musl à la place, une implémentation plus petite, écrite indépendamment, du même ensemble d'interfaces standard.
musl et glibc ne sont pas interchangeables au niveau de l’implémentation pour tout ce qu’elles exposent. Elles implémentent les mêmes interfaces standard, crypt() parmi elles, mais pas toujours avec un comportement interne identique derrière cette interface. Cet écart est habituellement invisible pour le code applicatif qui l’appelle. Ici, il a cessé de l’être.
Alpine fournit OpenSSH compilé contre musl. OpenSSH appelle l'interface standard crypt(3) exactement comme il le ferait avec n'importe quelle autre libc. La propre implémentation de musl de cette interface, spécifiquement ses chemins de code SHA-256 et SHA-512, est l'endroit où vit réellement le comportement d'échec spécifique que retracent les trois sections suivantes, et non dans le propre code d'OpenSSH ni dans le packaging qu'en fait Alpine.
Cela vaut la peine d'être précis, car il est facile d'exagérer : la vulnérabilité nécessite musl spécifiquement, pas simplement "compilé sans PAM". Le code de comparaison sans PAM d'OpenSSH existe sur toute build compilée ainsi, y compris, hypothétiquement, une build non standard avec glibc. Mais le comportement d'échec avec lequel entre en collision ce CVE, crypt() renvoyant une sentinelle d'un caractère au lieu de NULL, est un choix propre à l'implémentation de musl. Cette investigation n'a trouvé aucun comportement équivalent dans glibc, et la propre annonce de publication d'OpenSSH est explicite sur le fait que la population affectée est Linux, musl, et sans PAM, pas non-Linux, pas glibc, pas les systèmes utilisant PAM. [2]
Une pile à cinq couches, de haut en bas : Alpine Linux comme distribution, un sshd d'OpenSSH compilé sans PAM, l'interface de bibliothèque crypt(3), l'implémentation propre de crypt() de musl libc, et en bas son code de hachage spécifique à chaque algorithme, chaque couche étant reliée par l'appel ou la convention précise nommée entre elles. Aucune couche n'est marquée comme dangereuse : cette investigation n'a pas vérifié de façon indépendante le comportement en cas d'échec au niveau du code source propre de musl, crypt_sha256.c/crypt_sha512.c.
sshd d’OpenSSH, compilé avec --without-pamcrypt(attacker_password, stored_hash)crypt(3)__crypt_r()crypt() de musl libc$6$, $5$, ...)crypt_sha256.c / crypt_sha512.cCe que crypt() est censé faire en cas d’échec
crypt(3) est l'interface Unix traditionnelle pour calculer et vérifier ce hachage à sens unique, pas un chiffrement réversible, la valeur shadow stockée que vous avez déjà rencontrée deux sections plus haut. crypt(password, salt) calcule le hachage, et cette même fonction est réutilisée pour vérifier une tentative de connexion, en recalculant le hachage à partir du mot de passe tenté et du propre hachage stocké du compte réutilisé comme sel, puis en comparant les deux résultats par une simple comparaison de chaînes. Un sel existe pour que deux comptes partageant le même mot de passe ne se retrouvent pas non plus avec le même hachage stocké.
POSIX spécifie ce que crypt() est censé faire quand quelque chose se passe mal en interne durant ce calcul : renvoyer un pointeur nul, et définir errno pour indiquer pourquoi. Un appelant qui vérifie la présence de NULL sait que le calcul a échoué et ne devrait jamais traiter le résultat comme un hachage valide à comparer à quoi que ce soit d’autre.
La crypt() de musl ne respecte pas toujours ce contrat. Ce qu’elle fait à la place, et pourquoi cette déviation précise entre en collision avec les propres conventions de /etc/shadow de la section précédente, est le mécanisme que le reste de cet article retrace au niveau du code source, à partir de maintenant.
Pourquoi un compte valide se retrouve dirigé vers le chemin du sel de repli
Voici le premier détail non évident, et c'est un dérapage de logique d'un seul caractère, pas quelque chose de spectaculaire. Le propre contrôle de sélection de sel d'OpenSSH dans sys_auth_passwd() ne demande pas si le hachage stocké d'un compte est le substitut "*". Il demande si ce hachage fait au moins deux caractères de longueur.
La condition de garde est `if (authctxt->valid && pw_password[0] && pw_password[1]) salt = pw_password; else salt = NULL;`. Pour la chaîne d'un seul caractère "*", pw_password[0] est l'astérisque lui-même, qui est vrai, mais pw_password[1] est le NUL terminateur de la chaîne, qui est faux en C. Un caractère vrai. Un terminateur faux. C'est toute la condition, et elle est déjà fausse pour ce cas. Elle s'évalue à faux même si authctxt->valid est vrai, c'est-à-dire même pour un compte parfaitement réel et existant. Le code retombe dans la même branche salt = NULL utilisée pour un nom d'utilisateur qui n'existe pas du tout, non pas parce que quelque chose vérifie spécifiquement "*", mais comme effet secondaire d'une vérification de longueur qui se lit par coïncidence comme "est-ce assez long pour être plausiblement un hachage". [6]
Quand salt est NULL, xcrypt() appelle pick_salt(), reproduite intégralement plus bas avec son avis de licence BSD d’origine préservé. pick_salt() conserve un buffer statique de 32 octets limité à un processus et le renvoie immédiatement une fois déjà rempli. À son premier appel par processus, elle parcourt tous les comptes locaux via setpwent()/getpwent(), et copie le préfixe identifiant l’algorithme du premier hachage trouvé qui commence par "$" et contient un second "$", c’est-à-dire tout hachage au format crypt modulaire : MD5 ($1$), bcrypt ($2a$/$2b$/$2y$), SHA-256 ($5$) ou SHA-512 ($6$), pas un en particulier. Si rien sur le système n’a de hachage utilisable, elle se replie sur le sel traditionnel de style DES, "xx". [7]
L’avis d’Alpine formule la précondition comme nécessitant un autre compte avec spécifiquement un hachage $5$ ou $6$. Le code lui-même est plus large que cela : il accepte le premier hachage au format crypt modulaire dans l’ordre dans lequel getpwent() énumère les comptes, ce qui couvre aussi MD5 et bcrypt. Cet écart entre la formulation de l’avis et le comportement réel du code est une lecture propre à cette investigation, pas quelque chose qu’affirme directement l’une ou l’autre source, mais cela vaut la peine d’être signalé : une machine n’utilisant ailleurs que des hachages MD5 ou bcrypt satisferait quand même la précondition réelle, même si la formulation propre d’Alpine pourrait suggérer le contraire.
Une limite supplémentaire, propre à OpenSSH, sans rapport avec musl : auth_password() rejette tout mot de passe plus long que MAX_PASSWORD_LEN, défini à 1024, avant que sys_auth_passwd(), et donc crypt(), ne soit jamais atteint. C'est exactement pourquoi la fenêtre de l'avis plafonne à 1024 octets plutôt que de rester ouverte ; un mot de passe plus long ne s'approche jamais de la comparaison vulnérable. [8]
/* auth-passwd.c, sys_auth_passwd() -- pre-fix */
char *pw_password = authctxt->valid ?
shadow_pw(pw) : xstrdup(pw->pw_passwd);
...
if (authctxt->valid && pw_password[0] && pw_password[1])
salt = pw_password;
else
salt = NULL;
encrypted_password = xcrypt(password, salt);
...
ret = strcmp(encrypted_password, pw_password) == 0;
/* openbsd-compat/xcrypt.c -- Copyright (c) 2003 Ben Lindstrom, BSD license */
static const char *
pick_salt(void)
{
struct passwd *pw;
char *passwd = NULL, *p;
size_t typelen;
static char salt[32];
if (salt[0] != '\0')
return salt;
strlcpy(salt, "xx", sizeof(salt));
setpwent();
while ((pw = getpwent()) != NULL) {
if (passwd != NULL)
freezero(passwd, strlen(passwd));
if ((passwd = shadow_pw(pw)) == NULL)
continue;
if (passwd[0] == '$' && (p = strrchr(passwd+1, '$')) != NULL) {
typelen = p - passwd + 1;
if (passwd[1] == '2' && strlen(p + 1) >= 22)
typelen += 1 + 22;
strlcpy(salt, passwd, MINIMUM(typelen, sizeof(salt)));
explicit_bzero(passwd, strlen(passwd));
goto out;
}
}
out:
if (passwd != NULL)
freezero(passwd, strlen(passwd));
endpwent();
return salt;
}Là où le "*" de /etc/shadow rencontre le propre "*" de musl
sshd croit une chose précise à ce stade du code : la chaîne que crypt() renvoie pour un véritable mot de passe ne peut jamais être égale au propre hachage substitut du compte. L'attaquant contrôle exactement la seule entrée qui brise cette croyance : la longueur de son mot de passe.
L’avis d’Alpine déclare clairement l’état du compte vulnérable : un champ de mot de passe shadow qui est littéralement "*", la convention pour un compte sans hachage de mot de passe utilisable, un compte à clé uniquement. Il précise explicitement qu’un compte verrouillé, champ "!", n’est pas affecté. [5]
La crypt() de musl, dans ses chemins de hachage SHA-256 et SHA-512, renvoie la chaîne littérale "*", une constante de chaîne stockée en mémoire en lecture seule, au lieu de NULL, quand la vérification interne du hachage échoue. C’est exactement la violation de contrat décrite par la section précédente, désormais déclenchée par une condition concrète : un mot de passe d’entrée excessivement long. La vérification exacte par algorithme à l’intérieur de crypt_sha256.c et crypt_sha512.c n’a pas été lue de façon indépendante dans cette investigation ; ce qui est documenté ici se limite à l’avis d’Alpine, au propre article du chercheur signalant la faille, et à un fil de la liste de diffusion musl de décembre 2024 décrivant le même comportement de "*" en cas d’échec dans un contexte totalement différent, presque deux ans avant l’existence de ce CVE. [5] [4] [9] Le propre crypt.c de musl est un répartiteur léger et n’effectue lui-même aucune de ces vérifications, confirmé par inspection directe du fichier. [10]
L'échec qui produit "*" est déclenché par un mot de passe d'entrée excessivement long. L'avis d'Alpine et la fiche CVE donnent tous deux la même fenêtre : 257 à 1024 octets. La limite basse est le propre seuil interne de musl ; la limite haute, comme traité deux sections plus haut, est la limite préexistante MAX_PASSWORD_LEN d'OpenSSH, sans aucun rapport avec musl. [5] [1]
En mettant tout cela ensemble : un attaquant qui envoie un mot de passe dans cette plage d’octets, contre un compte dont le hachage stocké est "*", amène crypt() à calculer et renvoyer "*". La comparaison qui suit est `strcmp("*", "*")`, qui est vraie, par définition, car les deux côtés sont désormais exactement la même chaîne d’un caractère. L’authentification réussit, pour un compte explicitement configuré pour ne jamais accepter de mot de passe.
Aucune des deux moitiés de ceci ne paraît fautive isolément, et c'est précisément pourquoi c'est si facile à manquer. Les systèmes de types n'aident pas : les deux côtés de la comparaison sont des char*, et "*" est une chaîne syntaxiquement ordinaire, renvoyée avec succès, aussi bien du côté de la lecture shadow que du côté de crypt(). Rien ici ne ressemble à une condition d'erreur pour un analyseur statique. Et PAM aurait intercepté ce comportement par accident, via un bug pire : sur un système avec PAM activé, la même valeur de retour de musl fait au contraire planter pam_unix, car pam_overwrite_string() tente d'écrire dans ce littéral de chaîne en lecture seule, un déni de service plutôt qu'un contournement, et un problème déjà signalé indépendamment en décembre 2024. Les systèmes sans PAM n'ont pas d'appel intermédiaire équivalent, donc le même comportement musl sous-jacent produit un succès d'authentification silencieux plutôt qu'un plantage. [9]
/* musl libc, src/crypt/crypt.c -- MIT license */
char *crypt(const char *key, const char *salt)
{
static char buf[128];
return __crypt_r(key, salt, (struct crypt_data *)buf);
}
/* a thin dispatcher only -- the overlong-key / "*"-on-failure behavior
lives inside crypt_sha256.c / crypt_sha512.c, not here. That specific
file was not independently read in this investigation; see the text. */Deux côtés, chacun marqué d'un glyphe astérisque : à gauche, la convention propre de /etc/shadow selon laquelle un astérisque isolé signifie qu'aucun mot de passe ne sera jamais valide ; à droite, la sentinelle d'échec de crypt() dans musl, qui renvoie elle aussi un astérisque littéral dans certains cas d'échec. Les deux convergent vers un point mis en évidence indiquant strcmp d'astérisque et astérisque égal à zéro, avec le verdict que l'authentification réussit.
/etc/shadowUn astérisque isolé signifie : aucun mot de passe ne sera jamais valide.
crypt() dans muslRenvoie le même caractère au lieu de NULL en cas d’échec.
strcmp("*", "*") == 0
CORRESPONDANCE : L'AUTHENTIFICATION RÉUSSIT
Ce qui a réellement été construit, figé, et exécuté
Tout ce qui suit dans les deux prochaines sections provient d'une exécution réelle, pas d'une expérience de pensée : une image Alpine 3.24.4 à digest fixé, construite à partir d'une copie modifiée d'un dépôt public de preuve de concept, exécutée le 7 novembre 2026 et revérifiée de façon indépendante contre ses propres preuves enregistrées par hachage avant que quoi que ce soit ci-dessous ne soit écrit.
L'image de base était alpine:3.24, figée sur son digest de manifeste linux/amd64 plutôt que de se fier à un tag flottant. Rien ici n'a été laissé au bon vouloir d'un tag flottant. L'image de laboratoire elle-même a été délibérément construite avec OpenSSH 10.6p1, compilé avec --without-pam : la version d'avant correctif, vulnérable, pas la 10.6p2 déjà corrigée. Les versions de musl, libcrypto3, shadow et zlib ont été capturées directement depuis les propres métadonnées de paquet de l'image construite, pas approximées depuis un index de branche en direct.
Les entrées shadow pour root, alice, alpine et victim ont été réglées sur "*" ; bob a reçu un véritable hachage SHA-512 déterministe, construit avec un sel fixe fourni via un fichier plutôt qu'un argument de ligne de commande, comme contrôle de test négatif délibéré. sshd_config a réglé PasswordAuthentication yes et, modifié délibérément par rapport à la valeur par défaut propre de la preuve de concept publique, PermitRootLogin no, puisque root n'a jamais fait partie du périmètre de test approuvé de ce laboratoire.
L'isolation était plus stricte, pas plus lâche, que les valeurs par défaut de Docker : le conteneur fonctionnait sur un réseau bridge configuré --internal, sans route par défaut vers l'extérieur, Privileged false, CapDrop ALL, et seules CAP_NET_BIND_SERVICE, CAP_SETGID, CAP_SETUID et CAP_SYS_CHROOT ont été rajoutées, chacune justifiée contre le propre code source de séparation des privilèges d'OpenSSH 10.6p1 plutôt que supposée, plus no-new-privileges. Aucun mot de passe ni secret n'est apparu sur la ligne de commande d'aucun processus ; chaque fixture a été lue depuis un fichier.
Seuls alice et bob faisaient partie du plan de test approuvé. Ce que cette limite a réellement tenu, et n’a pas tenu, fait l’objet de sa propre section, placée juste après les résultats plutôt que silencieusement mêlée à eux.
Base image alpine:3.24, pinned to linux/amd64 digest
sha256:0f49f331d9212aff11b932863be4eb01cdf962969
aa86dab7d1ee21ac6f5f52b (resolves to Alpine 3.24.4)
Lab image sshole-lab:revision3
sha256:b973cd33dfd98a767b93cd5f95987d817f7b8eeda0
6e804e173b431ae6540e45
OpenSSH 10.6p1, built --without-pam (the pre-fix version,
deliberately -- this run tests the vulnerable build)
musl 1.2.6-r3
Capabilities --cap-drop=ALL
--cap-add=NET_BIND_SERVICE --cap-add=SETUID
--cap-add=SETGID --cap-add=SYS_CHROOT
--security-opt=no-new-privileges
Network sshole-lab-net, --internal (no default route out)
sshd_config PasswordAuthentication yes
PermitRootLogin noLe cas A a échoué. Le cas B a réussi. Bob n'a bougé dans aucun des deux sens.
Trois hypothèses sont entrées dans cette exécution, formulées explicitement comme des choses à tester, pas des choses à confirmer. Vrai, faux, ou non concluant étaient chacun, dès le départ, un résultat légitime et reportable.
Le cas A a testé un unique mot de passe excessivement long, non préparé : 300 octets, envoyés comme première et unique requête d’authentification par mot de passe d’une connexion fraîche, contre alice, dont le champ shadow est "*". Il n’a pas réussi. Le journal du serveur montre une connexion, une ligne "Failed password for alice", et la fermeture de la connexion. Aucune ligne "Accepted" n’apparaît nulle part pour cette connexion. Ceci est enregistré dans le dossier comme non reproduit.
Le cas B a testé le même mot de passe, préparé : un mot de passe court et délibérément incorrect envoyé d’abord, immédiatement suivi du même mot de passe excessivement long de 300 octets, comme deux requêtes d’authentification successives au sein d’une connexion SSH contre alice. Il a réussi. Le journal du serveur montre "Failed password for alice ... port 33394 ssh2" immédiatement suivi de "Accepted password for alice ... port 33394 ssh2", les deux sur le port source identique, du début à la fin, une seule connexion. La transcription client a confirmé le marqueur attendu d’exécution de commande en tant qu’alice. Ceci est enregistré comme reproduit.
Les cas C1 et C2 utilisaient bob, le contrôle à hachage réel. C1, le véritable mot de passe de bob, a réussi comme vérification de bon sens pour un contrôle positif. C2, le même mot de passe excessivement long de 300 octets qui venait de fonctionner contre alice, a échoué contre bob : "Accepted password for bob ... ssh2" pour le mot de passe correct, "Failed password for bob ... ssh2" pour celui excessivement long. Ensemble, ils confirment que le mot de passe excessivement long seul ne contourne pas l’authentification en général ; il ne correspond qu’au substitut dégénéré "*", exactement comme le prédit le mécanisme de cause racine ci-dessus.
Ces quatre résultats constituent une donnée issue d’une construction spécifique à digest fixé, exécutée une fois, le 7 novembre 2026. Ce n’est pas une affirmation sur la façon dont CVE-2026-123158 se comporte sur toutes les combinaisons de musl et OpenSSH partout ; c’est ce que cette image exacte, cette build exacte d’OpenSSH 10.6p1, et ces entrées exactes ont réellement fait, lors de cette unique exécution. Le dossier les enregistre délibérément avec ce périmètre attaché, et cet article conserve le même cadrage plutôt que de le généraliser.
Case A -- single-shot, alice, no priming -- not reproduced
Failed password for alice ... ssh2
[connection closes; no Accepted line for this connection]
Case B -- primed (short wrong, then overlong, one connection), alice -- reproduced
Failed password for alice ... port 33394 ssh2
Accepted password for alice ... port 33394 ssh2
Case C1 -- bob, real password, positive control -- reproduced
Accepted password for bob ... ssh2
Case C2 -- bob, same overlong password, negative control -- reproduced
Failed password for bob ... ssh2Un tableau de quatre cas de test issus d'une exécution de laboratoire. Le cas A, un unique mot de passe excessivement long non préparé contre alice, a journalisé un mot de passe refusé puis une connexion fermée : non reproduit. Le cas B, le même mot de passe précédé d'une première tentative courte et incorrecte, a journalisé un mot de passe refusé suivi d'un mot de passe accepté : reproduit. Le cas C1, le véritable mot de passe de bob, a journalisé un mot de passe accepté comme référence. Le cas C2, le même mot de passe excessivement long contre bob, a journalisé un mot de passe refusé comme contrôle négatif. Une question ouverte, suivie séparément, indique que la raison pour laquelle la préparation a compté lors de cette exécution reste non résolue au niveau du code source.
Pourquoi la préparation a compté lors de cette exécution reste non résolu au niveau du code source : pick_salt() renvoie un sel de façon synchrone à tout appel, et rien dans sa propre logique n'exige manifestement une préparation préalable.
Ce que cette exécution ne prouve pas, et les tentatives contre root que personne n'a révélées
Une exécution de laboratoire n'est digne de confiance qu'à la mesure de ce qu'elle admet n'avoir pas vérifié. Celle-ci admet trois choses précises franchement, plutôt que de laisser un ensemble propre de résultats dans le périmètre se substituer à un processus propre.
Le premier est une découverte de périmètre, pas de mécanisme. Le même fichier de preuves qui a enregistré le test à coup unique du cas A contre alice contient aussi, plus tôt dans la même fenêtre de capture, une séquence non révélée contre root : une ligne "Failed none for root", puis deux lignes consécutives "Failed password for root ... ssh2" au sein d’une connexion, puis la fermeture de la connexion. Aucune ligne "Accepted" pour root n’apparaît nulle part. La valeur soumise derrière ces deux tentatives est inconnue, et ce compte-rendu ne la devinera pas. root n’a jamais fait partie du périmètre approuvé de ce laboratoire, seulement alice et bob, à chaque niveau de l’autorisation de cette tâche, et le propre résumé de l’opérateur de l’exécution sur ce qui a été testé ne l’a jamais mentionné.
Rien n’a été compromis : les deux tentatives contre root ont échoué, et PermitRootLogin no a été confirmé dans la propre configuration effective du démon pour cette exécution. Une déviation de périmètre qui échoue quand même reste une déviation de périmètre. C’est exactement pourquoi le statut de laboratoire de ce dossier est enregistré comme "partiel" plutôt que "reproduit", une distinction qui vaut la peine d’être maintenue même là où les preuves dans le périmètre s’avèrent par ailleurs propres. Pourquoi cette séquence s’est produite, qu’une liste plus large de noms d’utilisateur par défaut intégrée à l’outillage ait tourné à la place des commandes personnalisées et délimitées du plan, ou tout autre chose, est une question à laquelle doit répondre directement celui qui a exécuté le laboratoire, pas un sujet sur lequel spéculer ici.
Le deuxième écart est une limitation de mesure, pas de périmètre. Le test du cas B a exécuté une boucle de sondage d'ID de processus concurrente spécifiquement pour capturer une preuve directe de savoir si le même processus du système d'exploitation a servi à la fois la tentative de préparation et la tentative excessivement longue, une preuve qui serait directement pertinente pour la question ouverte de la section suivante. Il a recueilli exactement un échantillon utilisable durant l'échange. Cela est cohérent avec une continuité de processus unique à travers les deux tentatives, pas une comparaison continue avant-après à travers l'intervalle entre elles. Suggestif. Pas concluant.
Le troisième écart concerne le propre chemin réseau du laboratoire, pas la vulnérabilité elle-même. Le plan spécifiait de publier le laboratoire sur 127.0.0.1:8222 ; l’opérateur de l’exécution rapporte que cette liaison n’a pas fourni de connectivité hôte fonctionnelle une fois le conteneur également attaché à un réseau --internal sans sortie, et a utilisé à la place un second conteneur de client de test jetable sur ce même réseau interne, atteignant le laboratoire par son nom DNS de conteneur. La propre sortie d’inspection du conteneur montre toujours que la liaison de port demandée a été acceptée dans HostConfig.PortBindings, tandis que NetworkSettings.Ports du même fichier, qui enregistre ce que Docker a réellement établi à l’exécution plutôt que ce qui a simplement été demandé, se lit comme vide : `{"22/tcp": []}`. Cela corrobore le rapport. Cela ne le prouve pas de façon concluante. Personne dans cette exécution n’a réellement tenté une connexion à 127.0.0.1:8222 depuis l’extérieur du conteneur pour confirmer qu’elle n’était vraiment pas active.
Rien de tout cela ne change ce que les cas A, B, C1 ou C2 ont montré. Cela change la mesure dans laquelle cette seule exécution peut servir de précédent pour la prochaine, et la réponse honnête est moindre que ce qu’offrirait une exécution totalement conforme au périmètre, spécifiquement sur le point du compte root.
Le correctif ne touche pas musl. Il ajoute une condition à une comparaison.
Le commit correctif, 4353fdc43ef646c43a8dda7025cc1a898caf1482, "avoid password authn bypass", écrit par Damien Miller le 5 novembre 2026, ne touche pas musl, ne touche pas pick_salt(), et ne touche pas le comportement de mot de passe excessivement long qui déclenche tout ceci. [11]
Il ajoute `#define MIN_PASSWORD_HASH_LEN 4` à côté du MAX_PASSWORD_LEN existant dans auth-passwd.c, et modifie la décision finale d’authentification pour exiger `strlen(pw_password) >= MIN_PASSWORD_HASH_LEN` en plus de la vérification strcmp() inchangée, où pw_password est le propre hachage stocké du compte, jamais le côté de la comparaison influencé par l’attaquant. [11]
Puisque "*" et "!" font tous deux exactement un caractère, `strlen(pw_password) >= 4` est faux pour tout compte que ce bug aurait pu jamais atteindre, et le `&&` court-circuite avant même que strcmp() ne s’exécute, quel que soit ce que crypt() renvoie pour n’importe quelle entrée, longue ou courte.
Le même changement de propriété mémoire, une copie sur le tas via xstrdup() et freezero() plutôt qu'un pointeur dont l'appelant n'est pas propriétaire, a été appliqué dans auth-passwd.c, openbsd-compat/port-uw.c (le chemin équivalent spécifique à UnixWare), et pick_salt()/shadow_pw() de xcrypt.c. La nouvelle vérification de longueur elle-même, cependant, n'est arrivée que dans le chemin générique d'auth-passwd.c, pas dans la comparaison séparée de port-uw.c. UnixWare n'est pas une cible de musl, donc cette collision précise ne peut pas se produire par ce chemin, ce qui est peut-être la raison pour laquelle la vérification n'y a pas été jugée nécessaire ; c'est une lecture propre et plausible de cette investigation, pas quelque chose que le commit affirme directement. [11]
Le message du commit lui-même affirme que le contournement "affecte certaines configurations et configurations d’exécution", et que l’équipe "ne considère pas que la cause racine soit au sein d’OpenSSH" tout en étant "bien équipée pour l’éviter". Ce cadrage correspond à ce que ce correctif fait réellement : il bloque exactement la collision décrite plus haut, une sentinelle d’un caractère entrant en collision avec un substitut shadow d’un caractère, en posant un plancher sur la longueur du hachage stocké plutôt qu’en validant que le hachage calculé est bien formé pour son algorithme déclaré. Si musl, ou tout logiciel compatible musl libc contre lequel on se lie, renvoyait un jour une autre courte chaîne, non substitut, sur un chemin d’échec différent, de quatre caractères ou plus, cette vérification précise ne l’attraperait pas. Cette investigation n’a trouvé aucun chemin de ce type existant actuellement ; c’est une limite de ce que garantit le correctif, pas un bug résiduel connu. [11]
/* auth-passwd.c, commit 4353fdc -- post-fix */
#define MIN_PASSWORD_HASH_LEN 4
...
ret = encrypted_password != NULL &&
strlen(pw_password) >= MIN_PASSWORD_HASH_LEN &&
strcmp(encrypted_password, pw_password) == 0;Alpine n'a pas fait sauter chaque branche directement à la 10.6p2
Alpine n'a pas déplacé toutes les branches supportées vers OpenSSH 10.6p2. Elle a rétroporté le correctif vers la version que chaque branche exécutait déjà, ce qui est une pratique standard de distribution : conserver la version de paquet existante de la branche, corriger le défaut précis.
Le traqueur de sécurité d’Alpine liste sept versions corrigées spécifiques à chaque branche plutôt qu’un seul numéro valable pour toute la flotte. Chaque branche actuellement supportée a reçu le rétroportage. [12]
Si vous vérifiez une flotte, faites correspondre la branche au tableau ci-dessous plutôt que de chercher la chaîne littérale "10.6p2" partout. Une machine de la branche 3.21-main exécutant la 9.9_p2-r1 rétroportée est corrigée, même si `sshd -V` n’affichera jamais 10.6.
| Branche | Version de paquet corrigée |
|---|---|
edge-main | 10.6_p2-r0 |
3.24-main | 10.3_p1-r2 |
3.23-main | 10.2_p1-r1 |
3.22-main | 10.0_p1-r11 |
3.21-main | 9.9_p2-r1 |
3.20-main | 9.7_p1-r6 |
3.19-main | 9.6_p1-r3 |
Ce qu’il faut vérifier dans vos journaux dès maintenant
L'avis propre d'Alpine donne des conseils de détection et d'atténuation suffisamment précis pour agir ce soir même, pas un conseil générique de "vérifiez vos journaux".
La logique de détection est la suivante : identifiez chaque compte dont le champ de mot de passe shadow est exactement "*", puis recherchez dans les propres journaux du démon une connexion par mot de passe réussie contre exactement ces comptes, la chaîne "Accepted password for" étant le marqueur à rechercher. Parallèlement, examinez l’usage de doas, les utilisateurs récemment créés, les nouveaux fichiers, et les changements d’authorized_keys. Si une connexion par mot de passe contre un compte sans mot de passe durant la fenêtre d’exposition ne peut pas être exclue, la position propre de l’avis est de traiter la machine comme compromise. [5]
L'atténuation consiste soit en une mise à jour de paquet suivie d'un redémarrage du démon, puisque mettre à jour le paquet seul ne remplace pas le processus déjà en cours d'exécution, soit en désactivant entièrement l'authentification par mot de passe comme solution provisoire, validée avant que le redémarrage ne prenne effet. [5]
Une nuance spécifique à root qui vaut la peine d'être précisée : le PermitRootLogin prohibit-password par défaut d'Alpine protège déjà root de ce contournement, à moins qu'un administrateur n'ait explicitement défini PermitRootLogin yes, même si root lui-même est livré en tant que root:* dans l'image de base. Cette valeur par défaut ne couvre cependant pas chaque chemin vers root : tout autre compte capable de se connecter par mot de passe et pouvant élever ses privilèges, par exemple via doas, crée toujours une exposition réelle même tandis que la connexion directe par mot de passe de root reste elle-même protégée. [5]
# Package upgrade, then restart -- upgrading alone does not
# replace the already-running daemon
apk upgrade -U -a
rc-service sshd restart
# Or: disable password authentication entirely
# PasswordAuthentication no (in sshd_config)
sshd -t
rc-service sshd restart
# Detection: look for a successful password login on an account
# that should never have one
grep "Accepted password for" /var/log/messages
# then cross-reference each username against /etc/shadow for a
# password field that is exactly "*"Pourquoi le même mot de passe a-t-il échoué au premier essai et fonctionné au second ?
Pourquoi exactement le même mot de passe excessivement long a-t-il échoué la première fois et réussi la seconde, contre le même compte, dans la même exécution ?
L’avis propre d’Alpine affirme que l’attaque "ne prend qu’une seule connexion... et ne nécessite aucune nouvelle tentative". Les deux scripts de preuve de concept écrits indépendamment et examinés pour ce dossier, celui fourni pour la construction du laboratoire et un autre référencé directement dans les propres références CNA de la fiche CVE, utilisent pourtant deux tentatives d’authentification par mot de passe au sein d’une connexion : un mot de passe court et incorrect d’abord, puis l’excessivement long, les deux attribuant cela dans leurs propres commentaires de code à une préparation d’un sel mis en cache avant la véritable tentative. Ce n’est pas nécessairement une contradiction. Les deux utilisent quand même un unique transport SSH, et "aucune nouvelle tentative" décrit sans doute le plus plausiblement le fait que l’attaque soit déterministe, ne nécessitant pas de deviner de façon probabiliste à répétition, plutôt que d’affirmer qu’un seul message d’authentification suffit. Mais cela ne règle pas non plus la question dans l’autre sens. [5] [13] [14]
Retracer le propre code de pick_salt() ne le résout pas non plus. La fonction recherche et renvoie un sel utilisable de façon synchrone, au sein du même appel, la première fois qu’elle s’exécute. Rien dans cette logique n’exige manifestement qu’un appel antérieur ait déjà eu lieu avant qu’un mot de passe long puisse être comparé à un sel nouvellement trouvé. Selon la propre logique du code, un unique mot de passe excessivement long non préparé, envoyé lors de la toute première requête d’authentification d’une connexion, devrait lui aussi atteindre pick_salt(), recevoir un sel en retour, et faire exécuter crypt() contre celui-ci, exactement comme le fait la seconde tentative dans les deux preuves de concept connues.
Ce que l’exécution de laboratoire a ajouté est réel, et c’est exactement une donnée : échec à coup unique (cas A), réussite préparée (cas B), dans cette construction spécifique. C’est une preuve appuyée par hachage que la préparation a compté dans cette exécution. Ce n’est pas une explication du pourquoi. L’énigme de pick_salt() ci-dessus reste intacte face à ce résultat. "Préparé fonctionne, à coup unique non" est cohérent avec plusieurs explications que cette investigation n’a pas confirmées de façon indépendante : un détail de la durée de vie propre au processus de séparation des privilèges d’OpenSSH (sshd répartit le travail d’authentification de chaque connexion entre un processus moniteur privilégié et un processus travailleur non privilégié ; cette investigation n’a pas retracé combien de temps chacun vit réellement), spécifiquement lequel de ces deux processus exécute pick_salt() et si sa durée de vie couvre une seule tentative d’authentification ou toute la connexion ; un autre comportement à état que ni les auteurs d’aucune des deux preuves de concept ni cette investigation n’ont identifié ; ou quelque chose de spécifique à cette construction et à ce minutage exacts qui pourrait ne pas se généraliser du tout.
Donc : cette question reste ouverte au niveau du mécanisme de code source après cette exécution de laboratoire, exactement comme elle l’était avant. Traitez la répartition réussite-préparée/échec-à-coup-unique comme une donnée qu’il vaut la peine de prendre au sérieux, pas comme une explication tranchée dans un sens ou dans l’autre. La prochaine étape utile, si quelqu’un la poursuit, est de retracer le modèle exact de processus de séparation des privilèges d’OpenSSH pour cette construction spécifique, ou de réexécuter le cas à coup unique avec un minutage différent pour voir si le résultat se maintient.
Deux fils plus mineurs restés en suspens, signalés plutôt que devinés : le propre code source de musl pour la vérification exacte de clé excessivement longue à l’intérieur de crypt_sha256.c et crypt_sha512.c n’a jamais été lu de façon indépendante dans cette investigation, seulement décrit par l’avis et le rapport de liste de diffusion de 2024. Et si le pseudonyme GitHub derrière l’article de sshhole.com est la même personne que celle créditée par son nom dans les notes de version d’OpenSSH est plausible, d’après le contenu technique correspondant, mais n’est confirmé par rien dans le dossier. Aucun des deux n’est déterminant pour une quelconque affirmation faite ici.
La leçon porte sur les contrats d'interface, pas sur musl ou OpenSSH
Corrigez musl quand elle publie un correctif. Corrigez OpenSSH, qui en a déjà un. Aucune des deux instructions n'est la véritable leçon d'ingénierie ici.
Le contrat d’interface de crypt() promet NULL en cas d’échec. La propre implémentation de musl de ce contrat ne tient pas toujours cette promesse, et la promesse dont dépend séparément la convention "*" de /etc/shadow, que ce caractère précis n’apparaisse jamais comme un résultat authentique de crypt(), s’est avérée dépendre entièrement du maintien de la première promesse. Aucun des deux sous-systèmes n’est fautif isolément : la convention "*" de /etc/shadow précède musl et n’a rien à voir avec elle, et le choix de sentinelle d’échec de musl n’a rien à voir avec OpenSSH ou les fichiers shadow. Le bug vit entièrement dans l’écart entre les deux, dans une hypothèse que personne n’avait écrite parce qu’elle avait simplement toujours tenu.
CWE-394, la classification que porte réellement ce CVE, est "Unexpected Status Code or Return Value", et c’est précisément ce que c’est. C’est aussi un bug "d’apparence inoffensive" dans deux sens distincts à la fois : un champ shadow "*" paraît inerte par conception, et le comportement "*" en cas d’échec de musl avait déjà été signalé, de façon indépendante, en décembre 2024, dans un contexte totalement différent, faisant planter pam_unix, presque deux ans avant que quiconque ne le relie à un contournement OpenSSH. La même violation de contrat sous-jacente, deux conséquences très différentes, dépendant entièrement de quel appelant l’a touchée.
La version pratique pour quiconque maintient une comparaison comme celle-ci : un contrat d’interface n’est aussi solide que la volonté de chaque appelant de le vérifier, pas aussi solide que la formulation propre du contrat. Si vous comparez une valeur calculée à une valeur stockée, et que l’un ou l’autre côté possède une sentinelle documentée du type "quelque chose s’est mal passé ici", vérifiez si cette sentinelle pourrait un jour tomber sur la valeur "rien à voir ici" de l’autre côté, avant de faire confiance à la comparaison. Ce n’est pas une vérification spécifique à musl ou à OpenSSH. C’est l’invariant réel qui, il s’avère, manquait à l’ensemble de ce CVE.
C'est exactement le type de frontière qu'une revue de code sécurisée est conçue pour trouver : pas le point d'injection évident, mais la comparaison à laquelle personne n'a pensé à s'interroger, parce que les deux côtés paraissaient toujours propres séparément. Si votre propre pile possède un chemin d'authentification ou de comparaison au niveau C qui n'a jamais été retracé fonction par fonction comme celui-ci l'a été ici, c'est précisément l'écart qu'une revue de code à ce niveau existe pour combler.
Sources
- [1] CVE Program / MITRE, CISA ADP : fiche CVE-2026-123158
- [2] Damien Miller : publication d’OpenSSH 10.6p2 (openssh-unix-dev)
- [3] Projet OpenSSH : notes de version 10.6p2
- [4] oz9un : *sshole (billet du chercheur ayant signalé la faille)
- [5] Alpine Linux : avis sur le contournement d’authentification par mot de passe d’OpenSSH
- [6] Projet OpenSSH / OpenBSD : auth-passwd.c
- [7] Projet OpenSSH : openbsd-compat/xcrypt.c
- [8] Miroir AOSP : auth-passwd.c (MAX_PASSWORD_LEN)
- [9] Liste de diffusion musl (Openwall) : crypt() renvoie "*" depuis une mémoire en lecture seule, décembre 2024
- [10] Projet musl libc : src/crypt/crypt.c
- [11] Projet OpenSSH : commit 4353fdc, "avoid password authn bypass"
- [12] Traqueur de sécurité d’Alpine Linux : CVE-2026-123158
- [13] halilkirazkaya : CVE-2026-123158 (PoC / dépôt de laboratoire)
- [14] predkambrij : sshhole-repro (PoC indépendant)
Besoin de vérifier ce point dans votre propre produit ?
Revue de code sécuritéVous voulez apprendre à trouver vous-même ce type de vulnérabilité ?
Formation individuelle à la revue de code sécurisé