
Exploit para CVE-2025-44203 visando uma condição de corrida no HotelDruid 3.0.0/3.0.7 que vaza credenciais de administrador e causa negação de serviço. Inclui um script de força bruta para recuperação offline de senhas.
HotelDruid 3.0.0 e 3.0.7 contêm uma condição de corrida no endpoint de configuração creadb.php, acessível antes da configuração ser concluída. Tem dois impactos, e ambos vêm da mesma corrida perdida: divulgação de informações sensíveis (através de mensagens de erro SQL detalhadas) e uma negação de serviço.
Ao enviar várias requisições POST de uma só vez, um atacante pode desencadear uma condição de corrida e ler dados sensíveis da resposta, incluindo o nome de usuário do administrador, hash e salt da senha. Se a senha escolhida durante a configuração do pacote Debian for fraca, ela pode ser recuperada offline com uma wordlist.
Quando o ataque é bem-sucedido, o administrador fica impossibilitado de fazer login com as credenciais que definiu durante a instalação (negação de serviço).
O pacote Debian deixa creadb.php acessível sem login até que a configuração seja concluída, e com o padrão do pacote C_UTILIZZA_SEMPRE_DEFAULTS="AUTO" ele executa novamente toda a criação do banco de dados a cada requisição. Essa rotina emite mais de 100 instruções SQL (criando e populando as tabelas) sem nenhum bloqueio em torno dela, então várias requisições que chegam juntas a executam ao mesmo tempo. Essa é a corrida.
Em uma máquina lenta ou ocupada, as execuções paralelas se sobrepõem e colidem no banco de dados SQLite. Depois que uma requisição começa a criar as tabelas e linhas, as instruções de uma requisição posterior falham em massa por vários motivos, como linhas já existentes ou o banco de dados estar bloqueado por outra gravação. Em cada falha, o wrapper de consulta do HotelDruid esegui_query() imprime a instrução falha completa na resposta HTTP. Isso significa que uma única resposta retorna com dezenas desses erros SQL, e um deles contém as informações sensíveis da conta de administrador.
update ...utenti set password = '<hash>', salt = '<salt>', tipo_pass = '5' where idutenti = '1'
É basicamente assim que o exploit detecta o sucesso. Ele procura na resposta por set password, que só aparece quando essa instrução precisa é ecoada, o que nunca acontece na saída normal de creadb.php.
O bloqueio vem da mesma falha. A linha do administrador é primeiro inserida com tipo_pass='n', e apenas o UPDATE acima a transforma em uma conta funcional (tipo_pass='5' com uma senha definida). O código que é executado logo após o UPDATE nunca verifica se funcionou. Ele cria o arquivo abilita_login, que ativa o login, exclui ini.php, que continha as credenciais do administrador, e posteriormente escreve ultimo_accesso, que fecha a configuração para sempre. Então, quando o UPDATE perde a corrida, a conta permanece em tipo_pass='n', que a página de login sempre rejeita, mesmo com a senha correta, enquanto o login está ativado e o arquivo de credenciais se foi. Nesse ponto, não há como voltar sem reinstalar.
É por isso que um vazamento bem-sucedido e o bloqueio são realmente o mesmo evento. Ambos acontecem quando aquele único UPDATE perde a corrida. Quando você obtém apenas o nome de usuário, significa que uma instrução anterior colidiu enquanto o UPDATE da senha ainda passou, então nada foi bloqueado.
Execute o exploit contra o alvo a partir da máquina atacante:
python3 exploit.py 192.168.1.1
onde 192.168.1.1 é o endereço IP da máquina que executa o HotelDruid.
Se funcionar, use brute.py com uma wordlist para tentar recuperar a senha em texto claro. Abra brute.py, defina salt como o salt obtido e final_hash como o hash obtido, então execute:
python3 brute.py rockyou.txt
onde rockyou.txt é sua wordlist.
Note que mesmo com o nome de usuário e senha corretos, você ainda não conseguirá fazer login, nem o administrador. A senha que você recupera está correta, mas a corrida bem-sucedida deixou a conta travada em tipo_pass='n', então a página de login a rejeita. Veja a seção Causa raiz.
A vulnerabilidade foi corrigida no HotelDruid 3.0.8. Aqui está o changelog, basta pesquisar por "CVE-2025-44203".
O auxiliar de bloqueio que ele usa (crea_lock_file(), um flock(LOCK_EX) bloqueante) já estava presente na versão 3.0.7 e era usado em outras partes do código. No entanto, creadb.php nunca o chamava. O patch faz duas coisas:
creadb.php: ele pega o bloqueio logo antes do provisionamento do administrador e o libera com distruggi_lock_file() depois. O que o faz funcionar é a verificação logo após o bloqueio ser adquirido. A requisição que entra primeiro exclui ini.php enquanto ainda detém o bloqueio, e cada requisição relê se ini.php existe antes do provisionamento, então as que estão na fila atrás dela pegam o bloqueio, encontram o arquivo já removido e pulam todo o bloco. Quando a segunda é executada, a configuração já está concluída e ela não faz nada.1, para aquelas duas chamadas esegui_query(), a que define o nome de usuário e a que define a senha e o salt. Essa flag diz ao wrapper de consulta para ficar quieto quando uma consulta falha. Ele ainda registra o erro no log do servidor, mas pula o echo que colocaria a instrução falha, incluindo hash e salt, na página.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);