Citrix NetScaler ADC: Two Zero-Days, One Post-Exploitation Chain

TL;DR: Two pre-authentication zero-days in Citrix NetScaler ADC — CVE-2026-88772 (DTLS heap overflow, CVSS 9.5) and CVE-2026-88771 (command injection via log processing) — have been actively exploited since early September. Mandiant/GTIG tracked the attackers deploying WHIPSHOT, a PHP web shell that reads commands from HTTP headers and relays through SLAPSHOT, a Python TCP tunneler, for internal reconnaissance. CISA added both to the KEV catalog on September 27 with a September 30 remediation deadline. No second zero-day hits this cleanly.

CVE-2026-88772: The DTLS Heap Overflow

This is the headline. CVE-2026-88772 lives in the NetScaler Packet Processing Engine (NSPPE), specifically in its DTLS handling. DTLS is enabled by default on VPN virtual servers, and it listens on UDP/443 — reachable by any internet client.

The exploit is elegant. NSPPE’s DTLS handshake parser checks a fragment_length field against the actual handshake message length. An attacker sends a malformed DTLS record header where the fragment length is smaller than the real message. A 120-byte handshake gets split into many one-byte fragments. Those fragments overflow a 35,840-byte buffer. With mprotect() used to bypass NX, the overflow diverts control flow to attacker shellcode running with root privileges on the underlying FreeBSD OS.

CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:H/A:H

Pre-authentication. Network-attacked. Low complexity. No user interaction. Heap overflow on default config. Root shellcode. That’s the kind of CVSS that makes the scoring feel conservative.

CVE-2026-88771: The Log Processing Injection

The second zero-day is a command injection via the ns_monuploadd_err.pl log-processing script. This script takes NSPPE crash core file names from system logs and concatenates them into shell commands. Attacker-controlled strings written to logs through unauthenticated interfaces get passed into eval() with no sanitization.

CVE-2026-88771 is also pre-authentication, but its attack surface is slightly narrower: it needs the attacker to trigger an NSPPE crash that writes a controlled string to the log. Still, CVE-2026-88772 reliably crashes NSPPE, so the two zero-days form a chain: overflow → crash → log injection → command execution. Both yield root.

The Post-Exploitation Chain: WHIPSHOT + SLAPSHOT

What makes this interesting isn’t just the zero-days. It’s what Mandiant found on compromised hosts.

WHIPSHOT is a PHP web shell that reads Base64-encoded commands from HTTP request headers. It suppresses errors, returns HTTP 404 despite successful execution, and relays to a local tunneler over loopback. The header names vary by installation variant:

  • HTTP_NSC_LDAP for nsginstaller*.deb variants
  • HTTP_NSC_CLIENTTYPE for nsgclient.sig
  • HTTP_X_UX and HTTP_X_ prefix for chunked transport

SLAPSHOT is a Python daemon launched with nohup and a Base64 exec stub. It binds an ephemeral port on 127.0.0.1, records it in /tmp/.uxdport, holds an flock on /tmp/.uxdlock, and speaks a 4-byte big-endian length-prefixed JSON protocol with commands: open, push, pull, exch, close, ping. The ping command includes a session ID, host, port, and data field. SLAPSHOT exits after 10 minutes without commands (UXD_IDLE_EXIT) and closes idle sessions after 15 minutes.

# SLAPSHOT protocol (simplified)
# 4-byte big-endian length prefix + JSON payload
{"cmd": "open", "host": "10.0.1.0/24", "port": [22, 445, 3389]}
{"cmd": "push", "data": "base64_encoded_scan_result"}
{"cmd": "exch", "sid": "abc123"}
{"cmd": "close"}

The purpose is clear: internal network reconnaissance and credential theft pivoting. Scanning/staging activity was observed from 143.198.7.94, with exploitation/installation from 157.254.167.12. The source doesn’t attribute the activity to a named actor, but the tradecraft is tight.

Detection Signals

Mandiant’s analysis gives us concrete things to look for:

  • Syslog: SSL_HANDSHAKE_FAILURE with ClientVersion: DTLSv1.0, Reason: 'Handshake failure-Internal Error'
  • NSPPE: process exits with pitboss ... NOT restarting NSPPE in /var/log/messages
  • Filesystem: /tmp/.uxdport (SLAPSHOT port file), /tmp/.uxdlock (flock file), /tmp/.whipshot.php (web shell), SUID bit on /bin/sh
  • Web config: php_flag/AddHandler/AliasMatch entries in /etc/httpd.conf
  • Process: sec_monitor rogue superuser account in ns_ namespace

The Timeline

DateEvent
2026-09-27Citrix publishes CTX697096 covering CVE-2026-88771 through CVE-2026-88778
2026-09-27CISA adds CVE-2026-88771 and CVE-2026-88772 to KEV catalog, September 30 remediation deadline
2026-09-28Bitsight and vendors confirm active exploitation
2026-09-30watchTowr publishes technical analysis and PoC for CVE-2026-88772
2026-09-30CISA KEV remediation deadline
2026-10-04Follow-on CVE-2026-88779 disclosed; updated builds 14.1-73.41, 13.1-64.28
2026-10-10Mandiant/GTIG publishes WHIPSHOT/SLAPSHOT tradecraft analysis

What You Should Do

  1. Check your version. NetScaler ADC 14.1 before 14.1-73.37, 13.1 before 13.1-64.23, or 13.1-FIPS/NDcPP before 13.1-37.279 are vulnerable.
  2. Upgrade to 14.1-73.37+, 13.1-64.23+, or 13.1-37.279+ (FIPS/NDcPP).
  3. Block UDP/443 inbound upstream as a compensating control for CVE-2026-88772 where DTLS isn’t needed.
  4. Scan for artifacts: /tmp/.uxdport, /tmp/.uxdlock, /tmp/.whipshot.php, SUID /bin/sh.
  5. Review logs from September 27 onwards for SSL_HANDSHAKE_FAILURE entries and NSPPE restart failures.

Why This Matters

This is what a modern zero-day looks like. Two independent pre-auth flaws, one chains into the other, attackers deploy a lightweight post-exploitation toolkit, and the whole thing runs on default configuration. DTLS is on by default. NSPPE crashes and writes to logs by default. The web shell hides behind a 404 by default.

Most importantly, the attackers aren’t just spraying and praying. SLAPSHOT’s JSON protocol, the header-based command relay, the flock-based locking — this is a coordinated operation, not a toolsmith’s playground. Someone wrote that Python daemon and knew exactly what it was for.

The second zero-day (CVE-2026-88772) is the one that makes you sweat. Heap overflow on UDP/443, mprotect to bypass NX, public PoC, active exploitation for weeks before disclosure. By the time you see the SSL_HANDSHAKE_FAILURE log entry, the attacker already has a root shell and is scanning your internal network through a TCP tunneler that lives for 10 minutes and leaves nothing but /tmp/.uxdport behind.

Two zero-days. One chain. One post-exploitation toolkit. That’s the kind of attack surface that reminds you why default configuration matters.