
Эксплойт для 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 базе данных. Как только один запрос начинает создавать таблицы и строки, запросы, пришедшие позже, массово терпят неудачу по разным причинам — строки уже существуют, база данных заблокирована другой записью. При каждом сбое обёртка запросов 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 — 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);