GO comment !
CVE-2026-93485 est une XSS stockée dans le cœur de WordPress, dans la fonction wpautop() (wp-includes/formatting.php). Un commentaire anonyme suffit à déposer une charge qui s’exécute dans le navigateur d’un administrateur dès qu’il ouvre l’article — sans clic de sa part. Dans une session admin, cette exécution de JavaScript se transforme en installation d’extension, donc en exécution de code à distance (RCE) sur le serveur.
- Sévérité : CVSS 3.1 = 7.1 (High), vecteur
AV:N/AC:L/PR:N/UI:R/S:C/C:L/I:L/A:L - Pré-authentifiée : aucun compte, aucun nonce
- Versions touchées : de 4.7.0 à 7.1.0, soit la quasi-totalité du parc
- Correctif : WordPress 7.1.1 (17 septembre 2026), avec rétroportages sur les branches maintenues
- Découverte : Rafie Muhammad (Awesome Motive), via le programme HackerOne de WordPress / Patchstack
Un PoC complet, Comment2Shell (DeathShotXD), a été publié le 23 septembre. https://github.com/DeathShotXD/Comment2Shell Il automatise toute la chaîne — scan de version, sonde XSS, exploit jusqu’au shell, shell interactif — et fournit aussi des artefacts pour la défense (template Nuclei, script IOC, requêtes de logs). C’est cet outil que je décortique ici, sous l’angle qui m’intéresse : comprendre le bug, puis savoir le détecter et le corriger.
Point d’honnêteté sur le vocabulaire : le PoC parle de « zero-click ». C’est vrai côté victime — la charge se déclenche au chargement de la page via
autofocus, sans que l’admin ne clique. Mais le vecteur CVSS porte bienUI:R: il faut quand même qu’un administrateur consulte l’article piégé pendant qu’il est connecté. « Sans clic » plutôt que « sans interaction ».
L’anatomie du bug : un désaccord entre l’enregistrement et l’affichage
Toute la vulnérabilité tient dans un écart classique mais redoutable : ce qui est validé à l’enregistrement n’est pas ce qui est rendu à l’affichage.
Quand un commentaire est soumis, WordPress le passe dans KSES (wp_kses), son filtre d’assainissement HTML. À ce stade, la charge est parfaitement inoffensive : elle n’utilise que des balises et attributs autorisés dans les commentaires — notamment blockquote avec son attribut cite, et code. Rien à signaler pour KSES : le HTML stocké est légitime.
Le problème survient à l’affichage, quand le contenu du commentaire traverse la chaîne de filtres comment_text. L’un d’eux est wpautop(), la fonction qui convertit les sauts de ligne en balises de paragraphe. Et wpautop() contient une expression régulière trop gourmande.
La cause racine
Dans wp-includes/formatting.php, la version vulnérable enveloppe les blockquotes ainsi :
// VULNÉRABLE (avant 7.1.1)
$text = preg_replace( '|<p><blockquote([^>]*)>|i', '<blockquote$1><p>', $text );
Le motif [^>]* capture « tout sauf un > ». L’astuce de l’attaquant : glisser un saut de ligne dans l’attribut cite. En amont, ce saut de ligne est transformé en marqueur interne de type commentaire HTML (<!-- wpnl -->). Or ce marqueur contient un >. La regex, qui s’arrête au premier > rencontré, coupe donc au mauvais endroit et injecte une balise <p> au milieu de l’attribut.
Résultat : la structure de l’attribut est brisée. Ce qui était une valeur de chaîne inoffensive se retrouve reparsé par le navigateur comme de nouveaux attributs HTML — en l’occurrence un gestionnaire d’événement onfocus accompagné d’un autofocus. Le navigateur, lui, ne voit plus une citation : il voit un élément qui prend le focus au chargement et exécute du JavaScript.
Le correctif rend la regex consciente des guillemets, pour qu’elle ne s’arrête plus sur un > situé à l’intérieur d’une valeur d’attribut :
// CORRIGÉ (7.1.1)
$text = preg_replace( '!<p><blockquote((?:[^>"\']|"[^"]*"|\'[^\']*\')*)>!i', '<blockquote$1><p>', $text );
Pourquoi KSES ne l’attrape pas
C’est le point le plus élégant — et le plus instructif — de cette CVE. KSES fait son travail correctement : au moment de l’enregistrement, la charge est du HTML valide et autorisé. Le saut de ligne n’est pas dans la table des caractères de syntaxe que wp_kses_hair() surveille. La transformation dangereuse n’a lieu que plus tard, à l’affichage, quand wpautop() réécrit le HTML déjà stocké. L’assainisseur et le formateur ne parlent pas la même langue.
La leçon dépasse WordPress : assainir une entrée une fois ne suffit pas si une étape ultérieure réécrit le balisage. Chaque transformation post-assainissement est une nouvelle surface d’injection.
La chaîne d’attaque, étape par étape
- Dépôt de la charge (non authentifié). Un commentaire anonyme est publié sur un article dont les commentaires sont ouverts. Aucun compte, aucun nonce.
- Le bug d’affichage se déclenche. Quand la page est rendue,
wpautop()casse l’attribut et fait apparaître un gestionnaire d’événement actif dans le HTML servi. - XSS dans la session admin. Grâce à
autofocus, leonfocusse déclenche au chargement, sans clic. Le JavaScript s’exécute avec les cookies de l’administrateur connecté. - De la session à la RCE. Le script récupère le nonce de la page d’installation d’extensions, construit une extension (une archive ZIP) directement en mémoire dans le navigateur, et la téléverse via l’interface d’administration. L’extension contient un webshell.
- Exécution de code. Le webshell est déposé dans
wp-content/plugins/, une commande est exécutée, puis — dans le PoC — le shell se supprime lui-même pour ne laisser aucune persistance.

Le déclencheur humain est le seul verrou : il faut qu’un admin ouvre l’article. Sur un site actif, ce n’est qu’une question de temps.
Le contournement de la modération
En théorie, les commentaires d’un nouveau contributeur passent en modération. En pratique, la charge n’a besoin que d’être visible par l’admin, et il existe plusieurs contournements :
- Réglage de modération désactivé. Si un administrateur a décoché « L’auteur d’un commentaire doit avoir un commentaire déjà approuvé » (
comment_previously_approved=0), n’importe quelle identité est auto-approuvée dès le premier commentaire. - Identité « connue ». Réutiliser le commentateur par défaut « A WordPress Commenter » que
check_comment()approuve automatiquement. - Prévisualisation auteur. Un commentateur déjà approuvé peut voir ses propres commentaires en attente via le cookie de hash de modération — de quoi déclencher la charge sans passer par l’approbation.
La formule de Patchstack résume bien l’affaire : la modération n’est pas un contrôle de sécurité. Elle filtre le spam, pas les exploits.
Suis-je concerné ? Détection et indicateurs
La question à se poser d’abord : ma version du cœur est-elle vulnérable, et ai-je des commentaires ouverts ? Un site en 4.7.0 → 7.1.0 avec des commentaires publics est exposé. Quelques signaux à surveiller côté serveur et journaux :
- Version du cœur. Vérifier la version effective (
Tableau de bord → Mises à jourouwp core version) et confirmer qu’elle est ≥ 7.1.1 (ou la version rétroportée de votre branche : 7.0.5, 6.9.8, 6.8.9, 6.7.8… jusqu’à 4.7.36). - Commentaires suspects en base. Rechercher des commentaires mêlant
blockquote, un attributciteet un gestionnaire d’événement :SELECT comment_ID, comment_author FROM wp_comments WHERE comment_content LIKE '%blockquote%onfocus%'; - Extensions apparues récemment. Une extension mono-fichier téléversée hors de tout déploiement légitime est un signal fort. Repérer les
.phpplus récents quewp-config.phpdanswp-content/plugins/, ainsi que les cycles rapides installation/suppression d’extension dans les logs. - Côté accès HTTP. Des
POSTvers/wp-comments-post.phpcontenant à la foisblockquote,onfocusetautofocus; desPOSTvers/wp-admin/update.php(téléversement d’extension) provenant d’adresses qui ne sont pas celles de vos administrateurs.
Aucune exploitation in-the-wild n’était confirmée à la publication, et la CVE ne figure pas au catalogue des vulnérabilités activement exploitées. Cela ne dispense pas d’agir : le PoC public abaisse fortement la barrière à l’entrée.
Remédiation
- Mettre à jour le cœur vers 7.1.1 (ou la version rétroportée de votre branche). C’est le seul correctif réel : la regex de
wpautop()devient consciente des guillemets et ne casse plus l’attribut. Aucun workaround ne le remplace. - Vérifier que l’auto-update a bien pris — ne pas le supposer. Les installations avec
DISALLOW_FILE_MODS, permissions restreintes ou auto-update désactivé n’ont pas reçu le correctif forcé. - Mesure d’attente si le patch tarde (défense en profondeur, pas un substitut) : fermer temporairement les commentaires sur les articles exposés, ou forcer la modération de tous les commentaires (
Réglages → Discussion) pour qu’aucune charge ne devienne visible sans validation humaine — en gardant à l’esprit que la modération filtre le spam, pas les exploits. - Après mise à jour, en cas de doute sur une compromission : auditer les comptes administrateurs, les extensions et fichiers récemment modifiés, les tâches cron WordPress, et faire tourner un scanner (Wordfence / Sucuri). Purger les commentaires piégés de la base.
Divulgation responsable
Cet article est publié à des fins défensives et pédagogiques. Il s’appuie exclusivement sur des informations déjà rendues publiques par l’éditeur et des chercheurs reconnus (WordPress Security Team, Patchstack, Rafie Muhammad). L’objectif est de comprendre la classe de bug — un désaccord entre assainissement et affichage — pour savoir la détecter et la corriger, pas de faciliter l’attaque. Toute reproduction doit se faire uniquement sur des systèmes vous appartenant, en environnement isolé. Attaquer un système tiers sans autorisation écrite est illégal.
À lire aussi
Autre vulnérabilité récente du cœur WordPress : wp2shell (CVE-2026-63030 & CVE-2026-60137) : chaîne SQLi + REST batch menant à une RCE pré-auth.
Sources
- WordPress News — WordPress 7.1.1 Maintenance and Security Release
- Patchstack — WordPress 7.1.1 Maintenance and Security Release
- NVD — CVE-2026-93485
- The Hacker News — Comment2Shell Flaw Can Turn Anonymous Comment XSS Into RCE