
استغلال لـ CVE-2025-44203 يستهدف حالة سباق في HotelDruid 3.0.0/3.0.7 مما يؤدي إلى تسريب بيانات اعتماد المسؤول والتسبب في رفض الخدمة. يتضمن سكريبت هجوم بالقوة العمياء لاستعادة كلمة المرور دون اتصال.
يحتوي HotelDruid 3.0.0 و 3.0.7 على حالة سباق في نقطة الإعداد creadb.php، والتي يمكن الوصول إليها قبل اكتمال الإعداد. لها تأثيران، وكلاهما ناتج عن نفس السباق الضائع: الكشف عن معلومات حساسة (من خلال رسائل خطأ SQL التفصيلية) وحرمان الخدمة.
عن طريق إرسال العديد من طلبات POST في وقت واحد، يمكن للمهاجم تشغيل حالة سباق وقراءة بيانات حساسة من الرد، بما في ذلك اسم مستخدم المسؤول وتجزئة كلمة المرور والملح. إذا كانت كلمة المرور المختارة أثناء تكوين الحزمة Debian ضعيفة، فيمكن استعادتها دون اتصال باستخدام قائمة كلمات.
عندما ينجح الهجوم، يُترك المسؤول غير قادر على تسجيل الدخول باستخدام بيانات الاعتماد التي عينها أثناء التثبيت (حرمان الخدمة).
تترك حزمة Debian ملف creadb.php قابلاً للوصول دون تسجيل دخول حتى ينتهي الإعداد، ومع الإعداد الافتراضي للحزمة C_UTILIZZA_SEMPRE_DEFAULTS="AUTO" يعيد تشغيل إنشاء قاعدة البيانات بالكامل في كل طلب. يصدر هذا الروتين أكثر من 100 عبارة SQL (إنشاء الجداول وملئها) دون أي قفل حولها، لذا فإن العديد من الطلبات التي تصل معًا تشغله في نفس الوقت. هذا هو السباق.
على جهاز بطيء أو مشغول، تتداخل عمليات التشغيل المتوازية وتتصادم على قاعدة بيانات SQLite. بمجرد أن يبدأ طلب في إنشاء الجداول والصفوف، تفشل عبارات طلب لاحق بكميات كبيرة لأسباب مختلفة مثل وجود الصفوف بالفعل أو قاعدة البيانات مقفلة بواسطة كتابة أخرى. عند كل فشل، يطبع غلاف الاستعلام esegui_query() في HotelDruid العبارة الفاشلة الكاملة في رد 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' مع تعيين كلمة مرور). لا يتحقق الكود الذي يعمل بعد التحديث مباشرة مما إذا كان قد نجح. يقوم بإنشاء ملف abilita_login، الذي يقوم بتشغيل تسجيل الدخول، ويحذف ini.php، الذي كان يحتوي على بيانات اعتماد المسؤول، ثم يكتب لاحقًا ultimo_accesso، الذي يغلق الإعداد للأبد. لذلك عندما يخسر التحديث السباق، يظل الحساب عند tipo_pass='n'، والذي ترفضه صفحة تسجيل الدخول دائمًا حتى مع كلمة المرور الصحيحة، بينما يتم تشغيل تسجيل الدخول واختفى ملف بيانات الاعتماد. في هذه المرحلة، لا توجد طريقة للعودة بدون إعادة التثبيت.
لهذا السبب فإن التسرب الناجح والإغلاق هما في الواقع نفس الحدث. كلاهما يحدث عندما يخسر ذلك التحديث الواحد السباق. عندما تحصل على اسم المستخدم فقط، فهذا يعني أن عبارة سابقة تصادمت بينما كان تحديث كلمة المرور لا يزال ناجحًا، لذلك لم يتم إغلاق أي شيء.
قم بتشغيل الاستغلال ضد الهدف من جهاز المهاجم:
python3 exploit.py 192.168.1.1
حيث 192.168.1.1 هو عنوان IP للجهاز الذي يعمل عليه HotelDruid.
إذا نجح، استخدم 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 موجودًا قبل التوفير، لذا فإن الطلبات التي تنتظره في الطابور تأخذ القفل، وتجد الملف قد اختفى بالفعل، وتتخطى الكتلة بأكملها. بحلول الوقت الذي يتم فيه تشغيل الطلب الثاني، يكون الإعداد قد اكتمل بالفعل ولا يفعل شيئًا.1، إلى استدعاءات esegui_query() تلك، الاستعلام الذي يعيّن اسم المستخدم والاستعلام الذي يعيّن كلمة المرور والملح. تخبر هذه العلامة غلاف الاستعلام بالبقاء صامتًا عندما يفشل استعلام. لا يزال يسجل الخطأ في سجل الخادم، لكنه يتخطى 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);