
CVE-2025-44203 के लिए एक शोषण जो HotelDruid 3.0.0/3.0.7 में रेस कंडीशन को लक्षित करता है, जो व्यवस्थापक क्रेडेंशियल्स को लीक करता है और सेवा अस्वीकार का कारण बनता है। इसमें ऑफ़लाइन पासवर्ड पुनर्प्राप्ति के लिए एक ब्रूट-फोर्स स्क्रिप्ट शामिल है।
HotelDruid 3.0.0 और 3.0.7 में creadb.php सेटअप एंडपॉइंट पर एक रेस कंडीशन है, जो सेटअप पूरा होने से पहले पहुंच योग्य है। इसके दो प्रभाव हैं, और दोनों एक ही हारी हुई रेस से आते हैं: एक संवेदनशील जानकारी का खुलासा (विस्तृत SQL त्रुटि संदेशों के माध्यम से) और एक सेवा अस्वीकृति।
एक साथ कई POST अनुरोध भेजकर, एक हमलावर रेस कंडीशन को ट्रिगर कर सकता है और प्रतिक्रिया से संवेदनशील डेटा पढ़ सकता है, जिसमें व्यवस्थापक उपयोगकर्ता नाम, पासवर्ड हैश और सॉल्ट शामिल हैं। यदि डेबियन पैकेज कॉन्फ़िगरेशन के दौरान चुना गया पासवर्ड कमजोर है, तो इसे बाद में एक वर्डलिस्ट के साथ ऑफलाइन पुनर्प्राप्त किया जा सकता है।
जब हमला सफल होता है, तो व्यवस्थापक इंस्टॉलेशन के दौरान सेट किए गए क्रेडेंशियल्स के साथ लॉग इन करने में असमर्थ रहता है (सेवा अस्वीकृति)।
डेबियन पैकेज creadb.php को सेटअप पूरा होने तक बिना लॉगिन के पहुंच योग्य छोड़ देता है, और पैकेज डिफ़ॉल्ट C_UTILIZZA_SEMPRE_DEFAULTS="AUTO" के साथ यह हर अनुरोध पर संपूर्ण डेटाबेस निर्माण को फिर से चलाता है। वह रूटीन बिना किसी लॉक के 100 से अधिक SQL स्टेटमेंट जारी करता है (टेबल बनाना और आबाद करना), इसलिए एक साथ आने वाले कई अनुरोध इसे एक ही समय में चलाते हैं। यही रेस है।
धीमी या व्यस्त मशीन पर, समानांतर रन ओवरलैप होते हैं और SQLite डेटाबेस पर टकराते हैं। एक बार जब किसी अनुरोध ने तालिकाओं और पंक्तियों का निर्माण शुरू कर दिया है, तो बाद के अनुरोध के स्टेटमेंट विभिन्न कारणों से बड़ी संख्या में विफल हो जाते हैं, जैसे पंक्तियाँ पहले से मौजूद हैं या डेटाबेस किसी अन्य लेखन द्वारा लॉक है। प्रत्येक विफलता पर, HotelDruid का क्वेरी रैपर esegui_query() पूरा विफल स्टेटमेंट HTTP प्रतिक्रिया में प्रिंट करता है। इसका मतलब है कि एक एकल प्रतिक्रिया दर्जनों उन SQL त्रुटियों के साथ वापस आती है, और उनमें से एक में व्यवस्थापक खाते की संवेदनशील जानकारी होती है।
update ...utenti set password = '<hash>', salt = '<salt>', tipo_pass = '5' where idutenti = '1'
काफी हद तक इसी तरह एक्सप्लॉइट सफलता का पता लगाता है। यह प्रतिक्रिया में set password खोजता है, जो केवल तब प्रकट होता है जब वह सटीक स्टेटमेंट गूँजता है, जो creadb.php के सामान्य आउटपुट में कभी नहीं होता।
लॉकआउट उसी विफलता से आता है। व्यवस्थापक पंक्ति को पहले tipo_pass='n' के साथ सम्मिलित किया जाता है, और केवल उपरोक्त UPDATE इसे एक कार्यशील खाते में बदलता है (tipo_pass='5' एक सेट पासवर्ड के साथ)। UPDATE के ठीक बाद चलने वाला कोड कभी जांच नहीं करता कि यह काम किया या नहीं। यह abilita_login फ़ाइल बनाता है, जो लॉगिन को चालू करता है, ini.php को हटाता है, जिसमें व्यवस्थापक क्रेडेंशियल थे, और बाद में ultimo_accesso लिखता है, जो सेटअप को हमेशा के लिए बंद कर देता है। इसलिए जब UPDATE रेस हार जाता है, तो खाता tipo_pass='n' पर रहता है, जिसे लॉगिन पृष्ठ सही पासवर्ड के साथ भी हमेशा अस्वीकार करता है, जबकि लॉगिन चालू है और क्रेडेंशियल फ़ाइल चली गई है। उस बिंदु पर पुनः स्थापित किए बिना कोई वापसी नहीं है।
यही कारण है कि एक सफल लीक और लॉकआउट वास्तव में एक ही घटना हैं। दोनों तब होते हैं जब वह एक UPDATE रेस हार जाता है। जब आपको केवल उपयोगकर्ता नाम मिलता है, तो इसका मतलब है कि एक पहले का स्टेटमेंट टकराया जबकि पासवर्ड UPDATE अभी भी गुजर गया, इसलिए कुछ भी लॉक आउट नहीं हुआ।
हमलावर मशीन से लक्ष्य के विरुद्ध एक्सप्लॉइट चलाएँ:
python3 exploit.py 192.168.1.1
जहाँ 192.168.1.1 HotelDruid चलाने वाली मशीन का IP पता है।
यदि यह काम करता है, तो सादा-पाठ पासवर्ड को पुनर्प्राप्त करने का प्रयास करने के लिए एक वर्डलिस्ट के साथ brute.py का उपयोग करें। brute.py खोलें, salt को आपके द्वारा प्राप्त सॉल्ट पर और final_hash को आपके द्वारा प्राप्त हैश पर सेट करें, फिर चलाएँ:
python3 brute.py rockyou.txt
जहाँ rockyou.txt आपकी वर्डलिस्ट है।
ध्यान दें कि सही उपयोगकर्ता नाम और पासवर्ड के साथ भी आप लॉग इन नहीं कर पाएंगे, और न ही व्यवस्थापक कर पाएगा। आपके द्वारा पुनर्प्राप्त किया गया पासवर्ड सही है, लेकिन सफल रेस ने खाते को tipo_pass='n' पर फँसा दिया, इसलिए लॉगिन पृष्ठ इसे अस्वीकार करता है। मूल कारण अनुभाग देखें।
कमजोरी को HotelDruid 3.0.8 में ठीक किया गया था। यहाँ चेंजलॉग है, बस "CVE-2025-44203" खोजें।
इसके द्वारा उपयोग किया जाने वाला लॉक हेल्पर (crea_lock_file(), एक ब्लॉकिंग flock(LOCK_EX)) पहले से ही संस्करण 3.0.7 में मौजूद था और कोड के अन्य भागों में उपयोग किया गया था। हालांकि, creadb.php ने इसे कभी कॉल नहीं किया। पैच दो काम करता है:
यह सेटअप रूटीन के चारों ओर एक एक्सक्लूसिव लॉक लेता है, ताकि एक साथ आने वाले अनुरोध रेस करने के बजाय अपनी बारी का इंतजार करें। फिक्स इसे अंततः creadb.php में उस हेल्पर को कॉल करके लागू करता है: यह एडमिन प्रोविज़निंग से ठीक पहले लॉक प्राप्त करता है और बाद में distruggi_lock_file() के साथ इसे छोड़ता है। इसे काम करने वाली बात लॉक लेने के तुरंत बाद की जाँच है। पहले आने वाला अनुरोध ini.php को हटा देता है जबकि वह अभी भी लॉक रखता है, और प्रत्येक अनुरोध प्रोविज़निंग से पहले पुनः पढ़ता है कि क्या ini.php मौजूद है, इसलिए इसके पीछे कतारबद्ध अनुरोध लॉक लेते हैं, फ़ाइल को पहले से गायब पाते हैं, और पूरे ब्लॉक को छोड़ देते हैं। जब तक दूसरा चलता है, सेटअप पहले ही हो चुका होता है और वह कुछ नहीं करता।
यह दो एडमिन UPDATE क्वेरीज़ को साइलेंस फ़्लैग पास करता है, ताकि भले ही उनमें से एक विफल हो, यह अब प्रतिक्रिया में क्रेडेंशियल प्रिंट न करे। फिक्स इसे उन दो esegui_query() कॉल्स में दूसरा तर्क, 1, जोड़कर लागू करता है, एक जो उपयोगकर्ता नाम सेट करता है और एक जो पासवर्ड और सॉल्ट सेट करता है। वह फ़्लैग क्वेरी रैपर को बताता है कि जब कोई क्वेरी विफल हो तो चुप रहना चाहिए। यह अभी भी सर्वर लॉग में त्रुटि रिकॉर्ड करता है, लेकिन यह echo को छोड़ देता है जो अन्यथा विफल स्टेटमेंट, हैश और सॉल्ट सहित, पृष्ठ में डाल देता।
creadb.php:
879: esegui_query("update $tableutenti set nome_utente = '".aggslashdb($admin)."' where idutenti = '1'",1);
897: esegui_query("update $tableutenti set password = '$passw', salt = '$salt', tipo_pass = '5' where idutenti = '1'",1);