
# Lab Docker A/B + PoC per CVE-2026-32475 (Elementor Pro Forms: upload arbitrario di file non autenticato -> RCE tramite desincronizzazione del loop di validazione/spostamento)
Lab Docker A/B che riproduce CVE-2026-32475 (CVSS 9.x, Patchstack; campo File Upload dei moduli Elementor Pro, interessate ≤ 4.2.1, corretto in 4.2.2 il 2026-08-19; segnalata da Tin Pham / TF1T). Non autenticato, nessuna interazione con l'utente.
Solo test autorizzati / scopi didattici. Questo lab gira interamente su
127.0.0.1in container Docker usa-e-getta. Non puntare il PoC a sistemi su cui non sei esplicitamente autorizzato a testare.build.shrecupera il sorgente di Elementor Pro da un mirror GPL pubblico; nessun codice premium viene ridistribuito in questo repository.
modules/forms/fields/upload.php. Due cicli iterano sullo stesso $_FILES['form_fields'][$id] rimodellato ma trattano una parte vuota (UPLOAD_ERR_NO_FILE) in modo diverso:
// validation() — riga ~269
foreach ( $_FILES['form_fields'][ $id ] as $index => $file ) {
if ( ! $field['required'] && UPLOAD_ERR_NO_FILE === $file['error'] ) {
return; // <-- abbandona TUTTA la validazione rimanente di questo campo
}
...
if ( ! $this->is_file_type_valid( $field, $file ) ) { // lista consentiti/bloccati estensioni
$ajax_handler->add_error( $id, 'This file type is not allowed.' );
}
}
// process_field() — riga ~418
foreach ( $_FILES['form_fields'][ $id ] as $index => $file ) {
if ( UPLOAD_ERR_NO_FILE === $file['error'] ) {
continue; // <-- salta SOLO la parte vuota, continua
}
$filename = uniqid() . '.' . pathinfo( $file['name'], PATHINFO_EXTENSION ); // estensione attaccante, nessun nuovo controllo
move_uploaded_file( $file['tmp_name'], trailingslashit( $uploads_dir ) . $filename );
}
Invia un campo upload opzionale con due parti — prima una vuota, poi il payload PHP. validation() incontra la parte vuota e fa return prima ancora di controllare il .php; process_field() fa continue oltre la parte vuota e sposta il .php in wp-content/uploads/elementor/forms/<uniqid()>.php. La blocklist get_blacklist_file_ext() (php,phtml,pht,shtml,…) viene eseguita solo dentro validation(), quindi viene completamente aggirata.
Completamente non autenticato: wp_ajax_nopriv_elementor_pro_forms_send_form (ajax-handler.php:295).
get_ensure_upload_dir() inserisce un .htaccess nella cartella forms contenente solo Options -Indexes + Header set Content-Disposition attachment. Questo è un suggerimento di download nel browser — su Apache + mod_php il .php viene comunque eseguito lato server; l'attaccante (curl) riceve semplicemente l'output del comando con un header di download. Il modulo forms non contiene nessuna pulizia unlink, quindi la shell persiste.
Una pagina pubblicata con un widget Form di Elementor Pro che ha un campo File Upload con Obbligatorio = No (il ramo ! $field['required']). La configurazione del campo viene letta lato server da _elementor_data, quindi l'attaccante non può attivarla — ma i campi "allegato opzionale" sono comuni.
Nome memorizzato = uniqid() = %08x%05x = (secondo unix)(microsecondo). Il secondo viene rivelato esattamente dall'header di risposta HTTP Date; solo il microsecondo (0–999999) è sconosciuto → un brute GET online limitato ≤10⁶. poc.py --recover ancora il microsecondo al tempo di arrivo della risposta (co-localizzato / sincronizzato NTP ⇒ secondi–minuti; qui: ~26k richieste) e trasforma la scrittura in esecuzione di codice remota.
| Servizio | Porta | Elementor Pro |
|---|---|---|
wp-vuln | http://127.0.0.1:8975 | 3.6.4 — vulnerabile (albero autentico; desincronizzazione validation() identica a ≤4.2.1) |
wp-patched | http://127.0.0.1:8976 | 3.6.4 con il fix equivalente alla 4.2.2 (return → continue, allineando i cicli) |
Stack: wordpress:php7.4-apache (mod_php) + MariaDB 10.6 + Elementor (gratuito) 3.6.8 + Elementor Pro 3.6.4.
Ogni variante ha un admin (admin/labpass) e una pagina pubblicata "CVE-2026-32475 Lab" (post_id=5, form_id=frm00001, campo upload field_cv, Obbligatorio=No).
Nota sulla versione. Nessun sorgente pulito 4.2.1/4.2.2 è pubblicamente ridistribuibile, quindi il lab esegue l'autentico albero 3.6.4, la cui desincronizzazione
validation()/process_field()è byte-identica al codice descritto da CVE-2026-32475 (il bug è latente da tempo; la 4.2.2 ha allineato i due cicli). La build A/B "corretta" applica esattamente quell'allineamento.
Richiede Docker + Docker Compose e rete in uscita (per scaricare le immagini, Elementor gratuito e il mirror del sorgente Elementor Pro). build.sh clona automaticamente l'albero autentico di Elementor Pro.
git clone https://github.com/dinosn/cve-2026-32475-elementor-pro-lab
cd cve-2026-32475-elementor-pro-lab
bash build.sh # clona plugin + compose up + installa WP/Elementor + crea pagina form (entrambe le varianti)
bash verify.sh # prova A/B: la vulnerabile scrive+esegue una shell; la corretta rifiuta. Pulisce.
# RCE non autenticata completamente remota (estrae automaticamente post_id/form_id/field_id dalla pagina):
python3 poc.py -t http://127.0.0.1:8975 --page-id 5 --recover -c "id; uname -a"
# dimostra solo il bypass della blocklist (lascia la shell per ispezione):
python3 poc.py -t http://127.0.0.1:8975 --post-id 5 --form-id frm00001 --field-id field_cv
docker-compose.yml — 2× (WordPress + MariaDB), vuln:8975 / patched:8976elementor-pro-vuln/ — Elementor Pro 3.6.4 autentico (vulnerabile); generato da build.sh, ignorato da gitelementor-pro-patched/ — stesso albero, validation() return→continue; generato da patch_pro.sh, ignorato da gitevidence/verify_transcript.txt — una registrazione di una esecuzione riuscita di verify.shinstall_wp.sh — installazione wp-cli di WP + Elementor gratuito + attivazione Pro + creazione pagina form + correzione permessi uploadssetup_page.php — costruisce la pagina form Elementor con un campo upload opzionalepatch_pro.sh — deriva l'albero corretto + stampa il diff di una rigapoc.py — upload desincronizzato non autenticato + recupero nome file uniqid() + RCEverify.sh — prova A/B con un oracolo filesystem indipendente (prova l'esecuzione, non solo la scrittura)build.sh — build one-shotcd /root/cve-2026-32475-lab && docker compose down -v