
Exploit für CVE-2025-44203, der eine Race Condition in HotelDruid 3.0.0/3.0.7 ausnutzt, Administratoren-Anmeldedaten preisgibt und einen Denial-of-Service verursacht. Enthält ein Brute-Force-Skript zur Offline-Passwortwiederherstellung.
HotelDruid 3.0.0 und 3.0.7 enthalten eine Race Condition im Setup-Endpunkt creadb.php, der bereits vor Abschluss des Setups erreichbar ist. Dies hat zwei Auswirkungen, die beide auf derselben verlorenen Race basieren: eine Offenlegung sensibler Informationen (durch ausführliche SQL-Fehlermeldungen) und eine Denial-of-Service.
Durch das gleichzeitige Senden vieler POST-Anfragen kann ein Angreifer eine Race Condition auslösen und sensible Daten aus der Antwort auslesen, einschließlich Administrator-Benutzername, Passwort-Hash und Salt. Wenn das während der Konfiguration des Debian-Pakets gewählte Passwort schwach ist, kann es anschließend offline mit einer Wortliste wiederhergestellt werden.
Wenn der Angriff erfolgreich ist, kann sich der Administrator nicht mehr mit den während der Installation festgelegten Anmeldedaten anmelden (Denial-of-Service).
Das Debian-Paket lässt creadb.php ohne Anmeldung erreichbar, bis das Setup abgeschlossen ist. Mit dem Paketstandard C_UTILIZZA_SEMPRE_DEFAULTS="AUTO" wird bei jeder Anfrage die gesamte Datenbankerstellung erneut ausgeführt. Diese Routine führt über 100 SQL-Anweisungen aus (Erstellen und Befüllen der Tabellen), ohne dass eine Sperre vorhanden ist, sodass mehrere gleichzeitig eintreffende Anfragen diese gleichzeitig ausführen. Das ist die Race.
Auf einem langsamen oder ausgelasteten Rechner überschneiden sich die parallelen Läufe und kollidieren mit der SQLite-Datenbank. Sobald eine Anfrage mit der Erstellung der Tabellen und Zeilen begonnen hat, schlagen die Anweisungen einer späteren Anfrage massenhaft fehl, aus verschiedenen Gründen wie bereits vorhandenen Zeilen oder einer durch einen anderen Schreibvorgang gesperrten Datenbank. Bei jedem Fehler gibt der Abfrage-Wrapper esegui_query() von HotelDruid die vollständige fehlgeschlagene Anweisung in die HTTP-Antwort aus. Das bedeutet, dass eine einzelne Antwort mit Dutzenden dieser SQL-Fehler zurückkommt, und einer davon enthält die sensiblen Informationen für das Administratorkonto.
update ...utenti set password = '<hash>', salt = '<salt>', tipo_pass = '5' where idutenti = '1'
So erkennt der Exploit im Wesentlichen den Erfolg. Er durchsucht die Antwort nach set password, das nur erscheint, wenn genau diese Anweisung ausgegeben wird, was in der normalen Ausgabe von creadb.php nie vorkommt.
Die Sperrung resultiert aus demselben Fehler. Die Admin-Zeile wird zunächst mit tipo_pass='n' eingefügt, und erst das obige UPDATE macht daraus ein funktionierendes Konto (tipo_pass='5' mit einem gesetzten Passwort). Der Code, der direkt nach dem UPDATE ausgeführt wird, überprüft nie, ob es funktioniert hat. Er erstellt die Datei abilita_login, die die Anmeldung aktiviert, löscht ini.php, das die Administrator-Anmeldedaten enthielt, und schreibt später ultimo_accesso, was das Setup endgültig abschließt. Wenn also das UPDATE die Race verliert, bleibt das Konto auf tipo_pass='n', was die Anmeldeseite selbst mit dem richtigen Passwort immer ablehnt, während die Anmeldung aktiviert ist und die Anmeldedatendatei fehlt. Ab diesem Punkt gibt es ohne Neuinstallation kein Zurück mehr.
Aus diesem Grund sind ein erfolgreicher Leak und die Sperrung im Wesentlichen dasselbe Ereignis. Beides passiert, wenn dieses eine UPDATE die Race verliert. Wenn Sie nur den Benutzernamen erhalten, bedeutet dies, dass eine frühere Anweisung kollidierte, während das Passwort-UPDATE noch durchkam, sodass keine Sperrung erfolgte.
Führen Sie den Exploit vom Angreifer-Rechner aus gegen das Ziel aus:
python3 exploit.py 192.168.1.1
wobei 192.168.1.1 die IP-Adresse des Rechners ist, auf dem HotelDruid läuft.
Falls es funktioniert, verwenden Sie brute.py mit einer Wortliste, um zu versuchen, das Klartext-Passwort wiederherzustellen. Öffnen Sie brute.py, setzen Sie salt auf den erhaltenen Salt und final_hash auf den erhaltenen Hash, und führen Sie dann aus:
python3 brute.py rockyou.txt
wobei rockyou.txt Ihre Wortliste ist.
Beachten Sie, dass Sie sich selbst mit dem richtigen Benutzernamen und Passwort nicht anmelden können – und der Administrator ebenso wenig. Das von Ihnen wiederhergestellte Passwort ist korrekt, aber die erfolgreiche Race hat das Konto auf tipo_pass='n' festgesetzt, sodass die Anmeldeseite es ablehnt. Siehe den Abschnitt „Grundursache“.
Die Schwachstelle wurde in HotelDruid 3.0.8 behoben. Hier ist das Changelog, suchen Sie einfach nach „CVE-2025-44203“.
Der dafür verwendete Sperr-Helfer (crea_lock_file(), ein blockierendes flock(LOCK_EX)) war bereits in Version 3.0.7 vorhanden und wurde in anderen Teilen des Codes verwendet. creadb.php rief ihn jedoch nie auf. Der Patch tut zwei Dinge:
creadb.php aufruft: Er holt die Sperre kurz vor der Admin-Bereitstellung und gibt sie mit distruggi_lock_file() danach wieder frei. Entscheidend ist die Überprüfung direkt nach dem Holen der Sperre. Die zuerst eintreffende Anfrage löscht ini.php, während sie noch die Sperre hält, und jede Anfrage liest erneut, ob ini.php existiert, bevor sie mit der Bereitstellung beginnt, sodass die dahinter wartenden Anfragen die Sperre nehmen, die Datei bereits als gelöscht vorfinden und den gesamten Block überspringen. Wenn die zweite Anfrage läuft, ist das Setup bereits abgeschlossen, und sie tut nichts.esegui_query()-Aufrufen ein zweites Argument, 1, hinzufügt, dem einen, das den Benutzernamen setzt, und dem anderen, das das Passwort und Salt setzt. Dieses Flag weist den Abfrage-Wrapper an, bei einem Abfragefehler still zu bleiben. Er zeichnet den Fehler weiterhin im Server-Log auf, überspringt jedoch das echo, das sonst die fehlgeschlagene Anweisung samt Hash und Salt in die Seite setzen würde.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);