
CVE-2023-6553 के लिए प्रूफ-ऑफ-कॉन्सेप्ट एक्सप्लॉइट और तकनीकी विवरण, WordPress Backup Migration प्लगइन <=1.3.7 में एक अनप्रमाणित PHP फ़ाइल समावेशन भेद्यता जो रिमोट कोड निष्पादन को सक्षम बनाती है।
प्लगइन: Backup Migration (backup-backup) ≤ 1.3.7
CVSS: 9.8 (गंभीर)
CWE: CWE-98 — Include/Require स्टेटमेंट के लिए फ़ाइलनाम का अनुचित नियंत्रण
प्रमाणीकरण आवश्यकता: कोई नहीं
प्रभाव: रिमोट कोड निष्पादन (RCE)
Backup Migration एक काफी लोकप्रिय WordPress प्लगइन है (~90,000+ सक्रिय इंस्टॉलेशन) जो उपयोगकर्ताओं को बैकअप प्रतियां बनाने में मदद करता है। बैकअप प्रक्रिया के दौरान, प्लगइन में backup-heart.php नामक एक फ़ाइल बैकग्राउंड में चलती है — यह HTTP हेडर के माध्यम से कॉन्फ़िगरेशन जानकारी प्राप्त करती है ताकि पता चल सके कि किस डायरेक्टरी का बैकअप लेना है, कॉन्फ़िग फ़ाइल कहाँ स्थित है, आदि।
समस्या इस तथ्य में निहित है कि यह फ़ाइल क्लाइंट द्वारा भेजे गए HTTP हेडर पर पूरी तरह भरोसा करती है, हेडर के मान को सीधे फ़ाइल पथ में ले जाती है, और फिर require_once() का उपयोग करके उस पथ से फ़ाइल लोड करती है। एक हमलावर को केवल दुर्भावनापूर्ण PHP कोड वाली डायरेक्टरी की ओर इशारा करते हुए Content-Dir हेडर भेजने की आवश्यकता है → सर्वर स्वचालित रूप से उसे शामिल (include) और निष्पादित कर देता है।
यह ध्यान देने योग्य है कि backup-heart.php फ़ाइल प्रमाणीकरण की आवश्यकता नहीं रखती — यह केवल जाँचती है कि अनुरोध विधि POST है या नहीं, बिना किसी nonce या उपयोगकर्ता विशेषाधिकार की पुष्टि किए। इंटरनेट पर कोई भी व्यक्ति इस पर अनुरोध भेज सकता है।
⇒ यह एक zero-click भेद्यता है।
| विशेषता | मान |
|---|---|
| CVE ID | CVE-2023-6553 |
| CVSS स्कोर | 9.8 (गंभीर) |
| प्लगइन | backup-backup (Backup Migration) ≤ 1.3.7 |
| प्रमाणीकरण | आवश्यक नहीं |
| उपयोगकर्ता सहभागिता | कोई नहीं (zero-click) |
| समाधान | संस्करण 1.3.8 |
PHP में include(), require(), require_once() जैसे फ़ंक्शन होते हैं जिनका उपयोग चल रहे प्रोग्राम में अन्य PHP फ़ाइलों को शामिल करने के लिए किया जाता है। जब इन फ़ंक्शनों में पारित फ़ाइल पथ बिना सत्यापन के उपयोगकर्ता इनपुट से आता है, तो एक हमलावर सर्वर को अपनी पसंद की कोई भी फ़ाइल शामिल करने के लिए मजबूर कर सकता है:
allow_url_include=On आवश्यक है (आमतौर पर डिफ़ॉल्ट रूप से अक्षम)।यह CVE LFI के अंतर्गत आता है — हमलावर require_once() को पारित पथ को नियंत्रित करता है, जो एक ऐसी PHP फ़ाइल की ओर इशारा करता है जिसे हमलावर ने सर्वर पर लिखने में सफलता प्राप्त की है।
कई डेवलपर्स सोचते हैं कि HTTP हेडर "आंतरिक" मेटाडेटा हैं जो केवल सर्वर और क्लाइंट को ज्ञात होते हैं। वास्तव में, हमलावर हेडर सामग्री का 100% नियंत्रण रखते हैं — वे कोई भी हेडर नाम और मान सेट कर सकते हैं। हेडर पर भरोसा करना फॉर्म इनपुट पर भरोसा करने जैसा ही है — इसे सत्यापित किया जाना चाहिए।
define() और PHP कॉन्स्टेंटdefine('NAME', $value) एक कॉन्स्टेंट बनाता है जिसका उपयोग पूरे एप्लिकेशन में किया जाता है। एक बार define हो जाने पर, मान को बदला नहीं जा सकता। यदि $value किसी हमलावर से आता है, तो उस कॉन्स्टेंट का उपयोग करने वाला हर स्थान प्रभावित होता है।
मैंने प्लगइन में सभी require और include स्टेटमेंट खोजने के लिए grep का उपयोग करके शुरुआत की:
grep -rn "require\|include" includes/

खोज परिणामों में कई require/include कॉल मिलीं। उन्हें देखने पर, अधिकांश banner/misc.php और banner/views/index.php में include_once कॉल थीं — ये हार्डकोडेड पथों वाले एडमिन UI रेंडरिंग कोड से संबंधित हैं, जिससे वे अशोषणीय (unexploitable) हैं।
हालाँकि, backup-heart.php में 2 पंक्तियों ने मेरा ध्यान आकर्षित किया:
includes/backup-heart.php:64: define('BMI_INCLUDES', BMI_ROOT_DIR . 'includes');
includes/backup-heart.php:118: require_once BMI_INCLUDES . '/bypasser.php';
पंक्ति 118 BMI_INCLUDES कॉन्स्टेंट के साथ require_once का उपयोग करती है — यदि यह कॉन्स्टेंट हार्डकोडेड होता, तो यह सुरक्षित होता। लेकिन पंक्ति 64 को देखने पर, मैंने पाया कि BMI_INCLUDES एक अन्य कॉन्स्टेंट, BMI_ROOT_DIR से निर्मित होता है। इसलिए हमें और आगे ट्रेस करने की आवश्यकता है: BMI_ROOT_DIR को उसका मान कहाँ मिलता है?
मैंने इसे ट्रेस करने के लिए फिर से grep का उपयोग किया:
grep -n "BMI_ROOT_DIR" includes/backup-heart.php
परिणाम:

Line 62: define('BMI_ROOT_DIR', $fields['content-dir']);
Line 64: define('BMI_INCLUDES', BMI_ROOT_DIR . 'includes');
पंक्ति 62 दिखाती है कि BMI_ROOT_DIR अपना मान $fields['content-dir'] से लेता है। यह एक वेरिएबल है, कोई निश्चित मान नहीं — हमें फ़ाइल खोलकर देखना होगा कि $fields में क्या होता है।
मैंने backup-heart.php को VS Code में पंक्ति 62 पर खोला:
// Line 62
define('BMI_ROOT_DIR', $fields['content-dir']);
// Line 64
define('BMI_INCLUDES', BMI_ROOT_DIR. 'includes');

यह स्पष्ट रूप से दिखाई देता है कि $fields['content-dir'] सीधे define() में जाता है। अब हमें यह निर्धारित करने की आवश्यकता है कि $fields वेरिएबल कहाँ असाइन किया गया है:
// Lines 7-9: Only checks POST method
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
exit;
}
// Lines 30-33: Reads ALL HTTP headers from the request
if (isFunctionEnabled('getallheaders')) {
$fields= getallheaders();
}
// Lines 42-46: Lowercases header names
foreach ($fieldsas $key=> $value) {
$buffer= $value;
unset($fields[$key]);
$fields[strtolower($key)] = $value;
}


इस बिंदु पर, मूल कारण बिल्कुल स्पष्ट हो जाता है: $fields में getallheaders() के माध्यम से प्राप्त सभी HTTP हेडर होते हैं — जो पूरी तरह से क्लाइंट द्वारा नियंत्रित होते हैं। यहाँ कोई wp_verify_nonce() नहीं है, कोई current_user_can() नहीं है, कोई मान्य पथ जाँच नहीं है — यह केवल POST विधि की पुष्टि करता है और हेडर को सीधे पढ़ लेता है।
Attacker sends POST request with header Content-Dir: /path/to/attacker/
↓
getallheaders() reads raw headers → $fields['content-dir'] = "/path/to/attacker/"
↓
define('BMI_ROOT_DIR', "/path/to/attacker/") ← no validation
↓
define('BMI_INCLUDES', "/path/to/attacker/includes")
↓
require_once "/path/to/attacker/includes/bypasser.php" ← executes PHP
↓
Attacker code runs with www-data privileges → RCE
मूल कारण सारांश: HTTP हेडर → define() → require_once(), बीच में कोई सत्यापन चरण नहीं।
मैंने हमले के प्रवाह की दृश्य पुष्टि के लिए Xdebug + VS Code का उपयोग किया। मैंने backup-heart.php में पंक्ति 62 और पंक्ति 118 पर 2 ब्रेकपॉइंट सेट किए, फिर curl का उपयोग करके एक्सप्लॉइट अनुरोध भेजा।
ब्रेकपॉइंट 1 — पंक्ति 62:
डीबगर ठीक define('BMI_ROOT_DIR', $fields['content-dir']) पर रुका। Variables पैनल में $fields वेरिएबल का विस्तार करने पर 22 तत्वों की एक ऐरे दिखाई दी — जिसमें क्लाइंट द्वारा भेजे गए सभी HTTP हेडर शामिल थे। विशेष रूप से:
content-dir = "/tmp/bmi/" — यह ठीक वही मान है जो हेडर के माध्यम से भेजा गया था, जो सीधे BMI_ROOT_DIR कॉन्स्टेंट को असाइन हो जाता है।content-abs = "/var/www/html/", content-configdir = "/tmp/bmi/", content-backups = "/tmp/bmi/back..." — ये सभी हमलावर द्वारा नियंत्रित हैं।इसे define() में पारित करने से पहले content-dir पर कोई सत्यापन या फ़िल्टरिंग चरण लागू नहीं किए जाते।