
Scanner haute fidélité pour CVE-2026-41940 (contournement d'authentification cPanel & WHM)
Un scanner haute-fidélité pour le contournement d'authentification cPanel/WHM référencé sous le nom CVE-2026-41940. Il identifie les hôtes vulnérables sans produire les faux négatifs courants dans les preuves de concept et détections publiques, et sans déclencher les mécanismes de verrouillage de compte et de liste blanche d'IP root qui interfèrent avec un scan naïf.
L'outil intègre également une chaîne d'exploitation distincte, activable sur option, pour un bogue de traversée de chemin CalDAV de la même famille sur cpdavd (ports 2079 en clair / 2080 TLS) — CVE-2026-29205 — qui permet à un attaquant distant de lire des fichiers arbitraires en tant que root une fois qu'une petite étape de configuration SMTP réussit. cPanel 11.134.0.26 corrige le bogue de durée de vie RAII sous-jacent, de sorte que la lecture s'exécute désormais sous l'identité du propriétaire du compte non privilégié ; la traversée elle-même atteint toujours cpdavd mais ne peut pas escalader au-delà de ce que ce compte peut déjà lire. La chaîne CalDAV est activée par --exploit et est désactivée par défaut car elle envoie de vrais emails et lit des fichiers sur des cibles confirmées — voir la section Mode exploit (actif).
La plupart des détections publiques de CVE-2026-41940 partagent trois problèmes. Ce scanner les aborde tous.
Vous pouvez lire notre article de blog sur cette technique de détection ici : https://slcyber.io/research-center/high-fidelity-check-for-the-cpanel-authentication-bypass-cve-2026-41940/
La configuration Apache par vhost de cPanel installe un ProxyPass qui transmet /___proxy_subdomain_whm à 127.0.0.1:2086 et /___proxy_subdomain_cpanel à 127.0.0.1:2080 indépendamment de l'en-tête Host de la requête. Le RewriteCond ne contraint que la réécriture qui mappe le sous-domaine d'administration sur le chemin proxy ; le ProxyPass lui-même est inconditionnel. Atteindre ces chemins sur n'importe quel vhost servi par un Apache géré par cPanel atteint le même backend vulnérable que les ports d'administration.
Les scanners qui ne sondent que les ports 2082/2083/2086/2087 signaleront un hôte comme non vulnérable lorsque ces ports sont pare-feu, alors que le bogue est entièrement accessible via le port 443. Ce scanner sonde les ports 2087, 2083 et les deux chemins proxy sur le port 443 par défaut.
cPanel fournit cphulkd, qui verrouille les comptes après un petit nombre de tentatives de mot de passe échouées, et authorized_whm_root_ips, qui restreint les connexions root à une liste configurée d'adresses sources. Un scanner qui exploite le contournement en tentant d'injecter une session pour root :
Ce scanner évite ces deux problèmes côté WHM en injectant expired=1 dans la charge utile de la session sous un nom d'utilisateur généré aléatoirement. L'injection de session est vérifiée en visitant l'URL cpsessXXXX résultante et en recherchant msg_code:[expired_session] dans le corps de la réponse, qui n'est présent que lorsque l'injection a réussi. Aucun compte réel n'est ciblé, donc aucun compte réel ne peut être verrouillé, et la liste blanche root est sans importance car aucune tentative de connexion root n'est effectuée.
Le démon cPanel (cpaneld, ports 2083 et le chemin /___proxy_subdomain_cpanel) exige que le nom d'utilisateur fourni corresponde à un compte cPanel existant sur le disque (-f /var/cpanel/users/$user). Un nom d'utilisateur root ne satisfera jamais cette vérification car root est un utilisateur système, pas un utilisateur cPanel. Les détections qui n'essaient que root produisent des faux négatifs sur cette surface. Ce scanner utilise une liste configurable de noms d'utilisateur cPanel courants contre la surface cPanel et revient au chemin du nom d'utilisateur aléatoire sur la surface WHM, qui n'a pas une telle restriction.
Pour chaque cible, le scanner effectue les étapes suivantes par surface :
GET /login et lit l'en-tête Set-Cookie pour soit whostmgrsession (WHM) soit cpsession (cPanel). Le cookie contient un composant de nom de session séparé par des virgules.GET / avec un en-tête Authorization: Basic dont la valeur décodée est <user>:\xff\nexpired=1. Le \nexpired=1 final est la charge utile d'injection de session. Le cookie de session de l'étape 1 est renvoyé inchangé.Location de la réponse et extrait le jeton cpsessXXXX.GET /<cpsessXXXX>/ avec le cookie d'origine et recherche msg_code:[expired_session] dans le corps. Sa présence prouve que l'injection de session a réussi et que l'hôte est vulnérable.Sur WHM (port 2087 et le chemin /___proxy_subdomain_whm sur le port 443), le nom d'utilisateur est un u aléatoire suivi de dix caractères hexadécimaux. Sur cPanel (port 2083 et le chemin /___proxy_subdomain_cpanel sur le port 443), le scanner parcourt sa liste de noms d'utilisateur et s'arrête au premier correspondant.
Par défaut, le scanner sonde les ports 2087, 2083 et 443 dans cet ordre et s'arrête dès qu'une surface confirme la vulnérabilité.
--exploit)CVE-2026-29205 — Mise à jour de sécurité cPanel/WHM WP2, 13 mai 2026. Correction dans cPanel 11.134.0.26. L'avis suit la même régression de baisse de privilèges
cpdavdque cette chaîne d'exploitation utilise.
Article complet sur le bogue et la chaîne d'exploitation : https://slcyber.io/research-center/new-age-of-collisions-reading-arbitrary-files-pre-auth-as-root-in-cpanel-cve-2026-29205
cpdavd sur les ports 2079 (HTTP en clair) et 2080 (TLS) fait confiance au chemin <principal>/<collection>/... qu'il construit lors du traitement des ressources CalDAV/CardDAV. En forgeant une requête dont le composant de chemin encode des segments .. et en la pointant vers un dossier maildir dont le nom sur le disque encode également une traversée (x-attachment-1-y), cpdavd peut être contraint de lire n'importe quel fichier sur le disque en tant que root, indépendamment de la propriété ou des permissions — y compris /etc/shadow, /etc/passwd et les spools de messagerie par utilisateur.
La défense en profondeur qui était censée réduire les privilèges au propriétaire du compte avant la lecture a silencieusement échoué : l'objet Cpanel::AccessIds::ReducedPrivileges était construit dans un contexte vide, donc son destructeur a restauré les privilèges root avant que la lecture ne s'exécute. cPanel 11.134.0.26 lie l'objet à une variable lexicale my $privs afin qu'il survive à la chaîne -f / stat / open / read ; sur les hôtes corrigés, la lecture s'exécute donc sous l'identité du propriétaire du compte non privilégié au lieu de root.
Le dossier vulnérable doit exister sur le disque avant que la lecture ne fonctionne. cPanel crée automatiquement un dossier nommé .x-attachment-1-y pour le destinataire <user>+x-attachment-1-y@<domain> la première fois qu'un email arrive à cette sous-adresse. La chaîne est donc :
info, admin, webmaster).<prefix>+x-attachment-1-y@<domain> à chaque candidat. Les réponses RCPT TO acceptées sont suivies.GET de traversée de chemin contre cpdavd sur les ports 2080 (TLS) et 2079 (en clair), sous les deux préfixes de collection /calendar/ et /addressbook/.Un succès renvoie les octets du fichier ; la découverte enregistre l'email utilisé, la collection, le nombre d'octets et les 200 premiers octets en aperçu.
Cette vérification est désactivée sauf si --exploit est passé. Lorsqu'elle est activée, elle nécessite un relais SMTP sortant fonctionnel (voir Fichier de configuration ci-dessous) car l'étape de création de dossier ne peut pas être ignorée.
L'exploit ne fonctionne que contre les comptes email virtuels réels configurés sous la fonction Email Accounts de cPanel. Les adresses de capture (catch-all) ne fonctionnent pas — une capture achemine via le routeur system_aliases d'Exim, n'atteint jamais le transport dovecot_virtual_delivery, et par conséquent ne déclenche jamais le chemin lda_mailbox_autocreate qui produit le dossier .x-attachment-1-y/.
Lorsque vous connaissez déjà un email virtuel valide sur la cible, transmettez-le avec --email :
python scanner.py --config scanner.ini --email [email protected] target.com
--email ignore entièrement l'énumération des SAN du certificat et la liste de préfixes et envoie exactement un message à l'adresse que vous avez fournie. Il peut être répété pour cibler plusieurs comptes connus. Le chemin ciblé est beaucoup plus fiable que le chemin par pulvérisation.
Sans --email, le scanner revient à pulvériser environ 15 préfixes courants (info, admin, webmaster, etc.) par domaine extrait du certificat TLS de l'hôte. La mesure d'Assetnote sur un échantillon de 200 hôtes exposant cpdavd a donné un taux de réussite de pulvérisation d'environ 10 % : un résultat propre en mode pulvérisation est une preuve faible que l'hôte est corrigé, et relancer avec --email contre un compte réel est le seul moyen fiable de confirmer.
Le mode exploit lit un fichier INI via --config :
python scanner.py --config scanner.ini --exploit example.com
Copiez scanner.ini.example vers scanner.ini, remplissez les identifiants SMTP, et éventuellement ajustez les valeurs par défaut de CalDAV. scanner.ini est dans .gitignore donc la copie renseignée reste locale. Le mot de passe SMTP peut également être fourni via la variable d'environnement SCANNER_SMTP_PASSWORD, qui prend la priorité uniquement lorsque le champ password dans le fichier est vide.
pip install -r requirements.txt
Python 3.8 ou ultérieur est requis.
Cible unique :
python scanner.py example.com
Cibles multiples via arguments positionnels :
python scanner.py host-a.example.com host-b.example.com:2083
Un fichier de cibles, une par ligne. Les lignes commençant par # sont ignorées :
python scanner.py -f targets.txt
Lecture des cibles depuis stdin :
cat targets.txt | python scanner.py
Une cible peut être soit un nom d'hôte soit hôte:port. Lorsqu'un port est spécifié, le scanner ne sonde que ce port ; sinon, il sonde les ports 2087, 2083 et 443.
-u, --users — noms d'utilisateur cPanel séparés par des virgules à essayer sur la surface cPanel. Par défaut, une petite liste intégrée.-U, --users-file — fichier avec un nom d'utilisateur cPanel par ligne.-p, --ports — ports séparés par des virgules à sonder lorsqu'aucun port n'est spécifié sur la cible. Par défaut 2087,2083,443.-t, --threads — threads par cible utilisés pour parcourir la liste de noms d'utilisateur contre la surface cPanel. Par défaut 10.-c, --concurrency — nombre de cibles scannées en parallèle. Par défaut 20.-T, --timeout — délai d'attente par requête en secondes. Par défaut 15.-o, --output — ajoute les cibles vulnérables, une par ligne, à ce fichier au fur et à mesure de leur découverte.--json — écrit un enregistrement JSON Lines par cible dans ce fichier.-q, --quiet — imprime uniquement les cibles vulnérables sur stdout. Les échecs de connexion et les cibles non vulnérables sont toujours enregistrés dans --json et comptés dans le résumé.Une ligne est écrite sur stdout par découverte, donc une cible exécutant les deux vérifications s'affichera deux fois :
[!] hôte cve-2026-41940 VULNÉRABLE (port 443)
[!] hôte caldav-traversal VULNÉRABLE via admin@hôte (lecture 1842o de /etc/passwd)
[+] hôte cve-2026-41940 NON VULNÉRABLE
[?] hôte cve-2026-41940 CONNEXION ÉCHOUÉE
La sortie --json est un enregistrement par cible avec un tableau findings :
{"target": "hôte", "status": "VULNÉRABLE", "findings": [
{"check": "cve-2026-41940", "status": "VULNÉRABLE", "detail": {"port": 443}},
{"check": "caldav-traversal", "status": "VULNÉRABLE",
"detail": {"email": "admin@hôte", "domain": "hôte", "collection": "calendar",
"file": "/etc/shadow", "bytes": 1218, "preview": "root:$6$..."}}
]}
status au niveau supérieur est le pire cas parmi toutes les découvertes.
Une ligne de résumé avec les totaux est écrite sur stderr à la fin. La barre de progression est affichée sur stderr et est automatiquement supprimée lorsque stderr n'est pas un terminal.
Le code de sortie est 0 si une cible est vulnérable, 1 si toutes les cibles accessibles étaient saines, et 2 si aucune cible n'a pu être atteinte.
Scannez une liste de cibles, écrivez les résultats dans un fichier et restez silencieux sur stdout :
python scanner.py -f targets.txt -o vulnerable.txt -q
Scannez avec une liste de noms d'utilisateur personnalisée contre la surface cPanel, parallélisme accru et sortie JSON pour traitement ultérieur :
python scanner.py -f targets.txt -U cpanel-users.txt -c 100 --json results.jsonl
Sondez un ensemble de ports non par défaut :
python scanner.py -p 2083,2087,8443 -f targets.txt
Exécutez la chaîne CalDAV de bout en bout contre une seule cible avec la pulvérisation d'email comme solution de repli (nécessite un scanner.ini renseigné) :
python scanner.py --config scanner.ini --exploit example.com
Exploitation ciblée contre un email virtuel connu — taux de réussite beaucoup plus élevé que la pulvérisation :
python scanner.py --config scanner.ini --email [email protected] example.com
Exécutez uniquement la chaîne CalDAV contre une liste d'hôtes cpdavd confirmés, en vidant les aperçus de /etc/passwd vers JSON :
python scanner.py --config scanner.ini --caldav-only \
-f cpdavd-hosts.txt --json caldav-results.jsonl
L'invocation par défaut scanner.py <cible> exécute uniquement le détecteur CVE-2026-41940. Il envoie les requêtes nécessaires pour confirmer l'injection de session et rien d'autre. Il ne se connecte pas en tant qu'utilisateur réel, ne cible pas le compte root, n'escalade pas vers un shell et n'accumule pas d'événements de mot de passe échoué contre un compte valide sur un système cible. Le marqueur qu'il recherche (msg_code:[expired_session]) est généré par l'application elle-même en réponse au champ de session expired=1 injecté et est le même indicateur que la page de connexion cPanel en amont utilise lorsqu'une session légitimement expirée est rejouée.
Passer --exploit (ou --caldav-only) débloque la chaîne de traversée de chemin CalDAV. Ce n'est plus un détecteur — c'est un exploit fonctionnel. Lorsqu'il est activé, le scanner, pour chaque cible où l'énumération de domaine réussit :
scanner.ini et envoie un court message par destinataire candidat (généralement 10 à 15 par domaine, 3 domaines par cible) ;--read-file configuré depuis chaque cible confirmée.Le fichier par défaut est /etc/shadow. Une lecture réussie renvoie les octets du fichier ; un corps vide indique soit une cible inaccessible, soit un hôte où le correctif de baisse de privilèges (cPanel 11.134.0.26, my $privs = …) est en place. La détection compare la longueur du corps reçu à zéro, et non au Content-Length annoncé par la réponse (qui est dérivé de stat() sur le chemin choisi par l'attaquant et sera renseigné même lorsque le open() ultérieur est refusé).
Pour tester l'accessibilité de la traversée indépendamment du correctif de baisse de privilèges — par exemple lorsque vous voulez savoir si cpdavd est accessible et si le prérequis maildir a été satisfait sur un hôte corrigé — relancez avec --read-file /etc/passwd. /etc/passwd est lisible par tous, donc il renvoie des octets sur les hôtes avant et après correction ; la combinaison des deux signaux (corps de /etc/shadow présent = lecture root avant correction ; /etc/shadow vide + /etc/passwd présent = traversée accessible mais correctif de baisse de privilèges appliqué) classe un hôte sans ambiguïté.
Le contenu et un aperçu de 200 octets sont écrits dans la sortie JSONL et imprimés sur stdout. L'exploitation CalDAV par domaine peut prendre une minute ou plus — il s'agit de l'échelle de tentatives en attente de livraison du courrier, pas d'un blocage.
Exécutez --exploit uniquement contre des actifs que vous possédez ou pour lesquels vous avez une autorisation écrite explicite de tester. Le trafic SMTP est journalisé par le relais configuré et par chaque système de messagerie destinataire ; les lectures de fichiers sont journalisées par cpdavd.
--no-progress — désactive la barre de progression.--exploit — active la chaîne de traversée de chemin CalDAV. Désactivée par défaut ; voir Mode exploit (actif) pour les effets secondaires que ce drapeau débloque.--config — fichier INI avec les identifiants du relais SMTP et les paramètres CalDAV. Voir scanner.ini.example.--read-file — fichier à exfiltrer lorsque la chaîne CalDAV réussit. Remplace la valeur dans le fichier de configuration. Par défaut /etc/shadow — un fichier réservé à root, donc une lecture réussie distingue la version avant correction (renvoie le contenu de shadow) de la version après correction (ouverture refusée, corps vide → signalé NON VULNÉRABLE). Utilisez --read-file /etc/passwd pour tester l'accessibilité de la traversée sans distinguer corrigé/non corrigé.--caldav-only — ignore la vérification de la CVE-2026-41940 et exécute uniquement la chaîne CalDAV. Implique --exploit. Utile pour relancer la chaîne contre une liste de cibles déjà connues comme accessibles via CalDAV.--email ADRESSE — compte email virtuel connu sur la cible. Ignore l'énumération des SAN du certificat et la liste de pulvérisation ; envoie exactement un message à ADRESSE et lit contre ce principal. Peut être répété. Implique --exploit. Voir Exploitation ciblée ou par pulvérisation.-v, --verbose — affiche la progression par domaine pour la chaîne CalDAV (liste des SAN du certificat, nombre de pulvérisations, échelle de tentatives).