Skip to content
KitploitKITPLOIT
ToolsBlog
Submit
ToolsBlog
Submit

Hacking, PenTest, and Cybersecurity Tools for Your Security Arsenal!

Kitploit is a directory of hacking, cybersecurity, and pentesting tools. Discover the latest project updates to find vulnerabilities, analyze systems, automate testing, and strengthen your security.

··Feeds·Contact·Privacy·© 2026 Kitploit

Tool Directory

Categories

View all categories
Loading categories
cve-2026-32475-elementor-pro-lab — A/B Docker lab + PoC for CVE-2026-32475 (Elementor Pro Forms unauthenticated arbitrary file upload -> RCE via validation/move loop desync) | Kitploit
Tools/GitHubGitHub/dinosn/cve-2026-32475-elementor-pro-lab
Vulnerability AnalysisExploitationWeb Application ExploitationWeb SecurityPenetration TestingLearning & EducationLabs & Practice
GitHubdinosn/cve-2026-32475-elementor-pro-lab

cve-2026-32475-elementor-pro-lab

A/B Docker lab + PoC for CVE-2026-32475 (Elementor Pro Forms unauthenticated arbitrary file upload -> RCE via validation/move loop desync)

View Repository
26h 43m agoNot yet reviewed

Most Popular

View all →

Discover the most used tools by our community.

Explore all tools

Browse our collection of tools

View all tools →
Share

CVE-2026-32475 — Elementor Pro Forms unauthenticated arbitrary file upload → RCE

A/B Docker lab that reproduces CVE-2026-32475 (CVSS 9.x, Patchstack; Elementor Pro Forms File Upload field, affected ≤ 4.2.1, fixed 4.2.2 on 2026-08-19; reported by Tin Pham / TF1T). Unauthenticated, no user interaction.

Authorized testing / education only. This lab runs entirely on 127.0.0.1 in disposable Docker containers. Do not point the PoC at systems you are not explicitly authorized to test. build.sh fetches the Elementor Pro source from a public GPL mirror; no premium code is redistributed in this repository.

Root cause — a validation/move loop desync

modules/forms/fields/upload.php. Two loops iterate the same reshaped $_FILES['form_fields'][$id] but treat an empty part (UPLOAD_ERR_NO_FILE) differently:

root@kitploit:~
// validation() — line ~269 foreach ( $_FILES['form_fields'][ $id ] as $index => $file ) { if ( ! $field['required'] && UPLOAD_ERR_NO_FILE === $file['error'] ) { return; // <-- abandons ALL remaining validation of this field } ... if ( ! $this->is_file_type_valid( $field, $file ) ) { // extension allow/deny list $ajax_handler->add_error( $id, 'This file type is not allowed.' ); } } // process_field() — line ~418 foreach ( $_FILES['form_fields'][ $id ] as $index => $file ) { if ( UPLOAD_ERR_NO_FILE === $file['error'] ) { continue; // <-- skips ONLY the empty part, keeps going } $filename = uniqid() . '.' . pathinfo( $file['name'], PATHINFO_EXTENSION ); // attacker ext, no recheck move_uploaded_file( $file['tmp_name'], trailingslashit( $uploads_dir ) . $filename ); }

Send one optional upload field with two parts — an empty one first, then the PHP payload. validation() hits the empty part and returns before ever checking the .php; process_field() continues past the empty part and moves the .php to wp-content/uploads/elementor/forms/<uniqid()>.php. The get_blacklist_file_ext() blocklist (php,phtml,pht,shtml,…) only runs inside validation(), so it is completely bypassed.

Fully unauth: wp_ajax_nopriv_elementor_pro_forms_send_form (ajax-handler.php:295).

Why it is RCE, not just a write

get_ensure_upload_dir() drops an .htaccess in the forms dir containing only Options -Indexes + Header set Content-Disposition attachment. That is a browser download hint — on Apache + mod_php the .php still executes server-side; the attacker (curl) simply receives the command output with a download header. The forms module contains no unlink cleanup, so the shell persists.

The one precondition

A published page with an Elementor Pro Form widget that has a File Upload field whose Required = No (the ! $field['required'] branch). Field config is read server-side from _elementor_data, so the attacker cannot toggle it — but "optional attachment" fields are common.

Filename recovery (fully-remote, no filesystem access)

Stored name = uniqid() = %08x%05x = (unix-second)(microsecond). The second is leaked exactly by the HTTP Date response header; only the microsecond (0–999999) is unknown → a bounded ≤10⁶ online GET brute. poc.py --recover anchors the microsecond on the response arrival time (co-located / NTP-synced ⇒ seconds–minutes; here: ~26k requests) and turns the write into remote code execution.

Lab layout

ServicePortElementor Pro
wp-vulnhttp://127.0.0.1:89753.6.4 — vulnerable (authentic tree; validation() desync identical to ≤4.2.1)
wp-patchedhttp://127.0.0.1:89763.6.4 with the 4.2.2-equivalent fix (return → continue, aligning the loops)

Stack: wordpress:php7.4-apache (mod_php) + MariaDB 10.6 + Elementor (free) 3.6.8 + Elementor Pro 3.6.4. Each variant has an admin (admin/labpass) and a published page "CVE-2026-32475 Lab" (post_id=5, form_id=frm00001, upload field field_cv, Required=No).

Version note. No clean 4.2.1/4.2.2 source is publicly redistributable, so the lab runs the authentic 3.6.4 tree, whose validation()/process_field() desync is byte-identical to the code CVE-2026-32475 describes (the bug is long-latent; 4.2.2 aligned the two loops). The A/B "patched" build applies exactly that alignment.

Build & run

Requires Docker + Docker Compose and outbound network (to pull images, Elementor free, and the Elementor Pro source mirror). build.sh clones the authentic Elementor Pro tree automatically.

root@kitploit:~
git clone https://github.com/dinosn/cve-2026-32475-elementor-pro-lab
cd cve-2026-32475-elementor-pro-lab
bash build.sh          # clone plugin + compose up + install WP/Elementor + create form page (both variants)
bash verify.sh         # A/B proof: vuln writes+executes a shell; patched rejects. Cleans up.

Exploit

root@kitploit:~
# fully-remote unauthenticated RCE (auto-extracts post_id/form_id/field_id from the page):
python3 poc.py -t http://127.0.0.1:8975 --page-id 5 --recover -c "id; uname -a"

# just prove the blocklist bypass (leave the shell for inspection):
python3 poc.py -t http://127.0.0.1:8975 --post-id 5 --form-id frm00001 --field-id field_cv

Files

  • docker-compose.yml — 2× (WordPress + MariaDB), vuln:8975 / patched:8976
  • elementor-pro-vuln/ — authentic Elementor Pro 3.6.4 (vulnerable); generated by build.sh, git-ignored
  • elementor-pro-patched/ — same tree, validation() return→continue; generated by patch_pro.sh, git-ignored
  • evidence/verify_transcript.txt — a captured passing verify.sh run
  • install_wp.sh — wp-cli install of WP + Elementor free + activate Pro + create form page + fix uploads perms
  • setup_page.php — builds the Elementor form page with an optional upload field
  • patch_pro.sh — derives the patched tree + prints the one-line diff
  • poc.py — unauth desync upload + uniqid() filename recovery + RCE
  • verify.sh — A/B proof with an independent filesystem oracle (proves execution, not just write)
  • build.sh — one-shot build

Cleanup / teardown

root@kitploit:~
cd /root/cve-2026-32475-lab && docker compose down -v
Download Tool