CVE-2026-88771 and CVE-2026-88772 are two critical remote code execution flaws in Citrix NetScaler ADC and NetScaler Gateway, and both were already being exploited when Citrix disclosed them on 27 September 2026 in bulletin CTX697096 alongside six other vulnerabilities. CISA added both to the Known Exploited Vulnerabilities catalog the same day and set a remediation due date of 30 September, three days later. Google Threat Intelligence Group and Mandiant place the start of the campaign in early September, which means attackers had roughly three weeks of unopposed access before a patch existed. Palo Alto’s Cortex Xpanse counted 50,277 internet-facing instances on the day of disclosure.
This post is three things. First, a plain-English explanation of what CVE-2026-88771 actually is, because the delivery mechanism is unusual and it changes how you detect it. Second, a Nuclei template you can run right now that identifies the firmware build of a NetScaler appliance without authenticating and without sending anything an incident responder would later have to explain. Third, the harder operational problem: finding every NetScaler on your attack surface, including the ones nobody remembers buying. Credit for the CVE-2026-88771 research goes to Sina Kheirkhah (@SinSinology) of watchTowr, who published the root-cause analysis on 28 September.
CVE-2026-88771 and CVE-2026-88772 at a glance
- CVE-2026-88771 – CWE-20, improper input validation leading to unauthenticated command execution as root. CVSSv4 9.5 Critical from Citrix as the CNA; CVSS 3.1 9.8 Critical from NVD. Vector
CVSS:4.0/AV:N/AC:L/AT:P/PR:N/UI:N/VC:H/VI:H/VA:H/SC:H/SI:H/SA:H. - CVE-2026-88772 – CWE-119, memory overflow in the DTLS path leading to remote code execution or denial of service. CVSSv4 9.5 Critical from Citrix; CVSS 3.1 8.1 High from NVD. Vector
CVSS:4.0/AV:N/AC:H/AT:N/PR:N/UI:N/VC:H/VI:H/VA:H/SC:H/SI:H/SA:H. - Affected products – NetScaler ADC and NetScaler Gateway, customer-managed. CVE-2026-88771 affects every deployment on an affected build, including the default configuration. CVE-2026-88772 requires DTLS, which is enabled by default on Gateway VPN virtual servers.
- Fixed builds – 14.1-73.37, 13.1-64.23, 14.1-73.37 FIPS, and 13.1-37.279 for the FIPS and NDcPP lines.
- No fix at all – branches 12.1 and 13.0 are end of life and receive no patch for either CVE.
- Exploitation status – both exploited as zero-days before disclosure. GreyNoise observed CVE-2026-88771 attempts on 24 September, three days before the bulletin. Rapid7 logs a first observation at 14:28:43 UTC on 20 September. A public proof of concept for CVE-2026-88771 was published on 27 September.
- Fix now – upgrade to the CTX697096 builds, then hunt for prior compromise, because patching does not evict an implant.
A disagreement worth knowing about
The two authorities do not agree on severity, and the gap is wide enough to change a patch queue. For CVE-2026-88772, Citrix scores 9.5 Critical under CVSSv4 while NVD scores 8.1 High under CVSS 3.1. That is one severity band apart. The driver is attack complexity: both agree it is high for CVE-2026-88772, but CVSSv4 weights the downstream subsequent-system impact that 3.1 has no vocabulary for.
Defend for the worse case. A vulnerability being exploited in the wild on an appliance that terminates your VPN is a critical, whatever the arithmetic says.
There is a second contradiction in circulation. One widely syndicated news report names CVE-2026-88778, the TCP initial sequence number prediction issue, as one of the two exploited flaws. Citrix, CISA, Rapid7, Tenable, Unit 42 and watchTowr all name CVE-2026-88771 and CVE-2026-88772. Treat CTX697096 and the KEV catalog as authoritative and the news report as an error, but patch all eight regardless, since they ship in the same firmware image.
What is CVE-2026-88771?
NetScaler runs a Perl script called ns_monuploadd_err.pl whose job is to process crash and error information from the packet engine. When a packet engine dies, the appliance writes a diagnostic line to a log, and the script later parses that log to work out which engine died and which process ID to clean up.
The flaw is in how the script turns that log text into a filename. It read the engine name and process ID straight out of the log line, built a filename from them, and interpolated the result into a shell command inside backticks around find. Nothing validated that the text taken from the log looked like an engine name.
That is a textbook command injection, with one twist that matters enormously for detection: the attacker does not need to reach the Perl script, and there is no single endpoint to block. The attacker’s job is only to get their text into the log file. watchTowr’s analysis notes that failed login attempts, users blocked by rate limiting, request parameters and even User-Agent headers can all end up in that log. An unauthenticated request to a pre-auth surface is enough. The script executes the result later, as root.
The delay is real and it is long. The public proof of concept warns that a planted command may take up to 24 hours to be picked up, depending on when the log gets parsed. watchTowr indicated they found a way to force immediate execution and deliberately withheld it.
Three consequences follow, and they are the practical reason this post exists:
- You cannot reliably WAF this. There is no one path, parameter or header to filter, because many unrelated pre-auth code paths write user-controlled strings to the same log.
- Network detection and execution are separated in time. An IDS rule may fire a day before the command runs. If you see the injection, you are not necessarily too late, which is unusually good news.
- Your logs are now both evidence and weapon. The log file investigators need in order to establish prior compromise is the same file an attacker writes into. Hold that thought; it decides how we built the detection template below.
Citrix’s fix in 14.1-73.37 is the right one. The shell pipeline of grep, sed and awk is replaced with native Perl parsing, a strict regular expression that captures only a packet engine name matching NSPPE-\d{2} plus a numeric process ID, and a call to find using Perl’s list form, which never invokes a shell at all.
CVE-2026-88772 is a different animal: a memory overflow reached through DTLS, the datagram variant of TLS that NetScaler Gateway enables by default on VPN virtual servers. Malformed records corrupt packet engine heap memory, which yields either code execution or a crash. Attack complexity is genuinely high, which is why the proof-of-concept tooling published for it generates detection artifacts rather than a working exploit.
Affected and fixed versions
| Product line | Affected builds | Fixed build |
|---|---|---|
| NetScaler ADC and Gateway 14.1 | before 14.1-73.37 | 14.1-73.37 |
| NetScaler ADC and Gateway 13.1 | before 13.1-64.23 | 13.1-64.23 |
| NetScaler ADC 14.1-FIPS | before 14.1-73.37 FIPS | 14.1-73.37 FIPS |
| NetScaler ADC 13.1-FIPS and NDcPP | before 13.1-37.279 | 13.1-37.279 |
| NetScaler ADC and Gateway 12.1 | all | none, end of life |
| NetScaler ADC and Gateway 13.0 | all | none, end of life |
Four separate floors across four lines, and that is exactly where patch-gap tooling tends to go wrong. A naive “anything below 14.1-73.37 is vulnerable” check does the wrong thing twice: it flags a perfectly patched 13.1-64.23 appliance, and if you reverse the comparison it clears a vulnerable 13.1-37.250 FIPS box. Note also that Citrix writes the FIPS floor with dots, 13.1-37.279, in prose that elsewhere uses the hyphenated form. NVD’s CPE data carries the same inconsistency, and one NVD CPE range marks 14.1-73.37 FIPS itself as affected with versionEndIncluding, which contradicts the bulletin prose naming that exact build as the fix. Any tool doing string-to-version coercion across these lines needs to be tested against the boundaries rather than trusted.
Two more traps:
An upgrade that is staged is not an upgrade that is running. On an HA pair, the appliance answering your probe is the one that happens to hold the VIP. An unpatched standby node is exposed the moment it takes over. Scan both node addresses, not just the virtual IP.
Patching for the previous NetScaler CVE does not help. An appliance updated for CVE-2026-19490 earlier in September is still fully affected by both of these. CTX697096 is a separate firmware floor.
How the exploit chain works
The shape of the attack, with the payload position redacted:
POST /nf/auth/doAuthentication.do HTTP/1.1
Host: vpn.example.com
Content-Type: application/x-www-form-urlencoded
login=[REDACTED: a crash-diagnostic-shaped string followed by a shell
metacharacter and the attacker's command]&passwd=x
The mechanism in prose: the submitted value is crafted to imitate a packet engine crash diagnostic, so that when it is written to the error log it will later be read back by ns_monuploadd_err.pl as if it were a legitimate engine name. Because the script interpolated that value into a shell command rather than passing it as an argument list, the attacker’s shell metacharacter terminates the intended command and the remainder runs as root. The authentication endpoint above is one of several pre-auth surfaces that will do; it is not the vulnerability, only a convenient pen for the ink.
We are deliberately not publishing a working exploit here. A public proof of concept already exists, and reproducing it adds nothing for defenders while shortening the distance for everyone else. The same editorial line applies to every advisory in this series, including our JFrog Artifactory authentication bypass advisory, where the upstream community template was an active exploit that minted admin tokens and ours was a passive version check.
What attackers did after landing is better documented than usual and worth reading as a hunting list. Rapid7 observed command injection running tar over /flash/nsconfig, writing the archive under the web root so it could be retrieved with a single unauthenticated GET. That archive contains ns.conf, TLS private keys and stored credentials. Mandiant and others report a PHP web shell at /var/netscaler/logon/LogonPoint/custom/.ctxs.receiver with SHA256 ed082f744f035035900f67edf438f2f7d0528ac501234f63d476d65273cdb9a1, modifications to httpd.conf, and two named implants, WHIPSHOT and a Python tunneler called SLAPSHOT. Some web shell variants answer attacker requests while returning HTTP 404, so a status-code-only sweep of the web root will miss them.
Am I affected? Quick manual checks
The authoritative check is local. On the appliance:
show ns version
Compare against the table above, being careful about which line you are on. Then confirm DTLS state for CVE-2026-88772, remembering that this only narrows the second CVE:
show vpn vserver <name>
A Gateway virtual server is exposed unless its configuration shows -dtls OFF, and any virtual server of type DTLS is exposed by definition. Disabling DTLS is not remediation. CVE-2026-88771 remains independently exploitable and needs no DTLS at all.
The external fingerprint is harder, which is the interesting part. NetScaler advertises no version to unauthenticated clients. The HTTP response headers carry nothing. The login HTML and the JavaScript bundles are byte-identical across builds. That is precisely why Shodan and Censys records for these appliances have no version field, and why a version check adds genuine value over a banner grab.
There are two usable oracles, and neither requires authentication.
The better one is the firmware compile timestamp, a technique originally published by Fox-IT. Every build ships a static language resource at /vpn/js/rdx/core/lang/rdx_en.json.gz. The gzip format stores a modification time in the header, bytes 4 through 8, and the firmware build process sets it at compile time. That value maps one to one onto a published build. You can read it by hand:
curl -sk https://vpn.example.com/vpn/js/rdx/core/lang/rdx_en.json.gz \
| head -c 8 | tail -c 4 | xxd -e -g4 | awk '{print strtonum("0x"$2)}'
The second oracle is the pre-logon endpoint analysis package. Gateway has to serve the EPA client to users who have not authenticated yet, so /epa/scripts/linux/nsepa.deb is readable pre-auth, and its length changes between builds. Reading the Content-Length header is enough; there is no need to download eleven megabytes.
What the external signal can and cannot tell you: a resolved build is solid evidence of the firmware level and nothing more. It says nothing about whether the box was compromised during the three weeks before the patch existed. And a failure to resolve a build means “unknown, go and check by hand”, never “patched”. More on that blind spot below, because it is the honest limit of this whole approach.
Nuclei detection template for CVE-2026-88771
Two templates ship: a reusable firmware fingerprint, and a CTX697096 verdict built on top of it. Both are passive. Both issue nothing but plain GET requests for static files.
The verdict template works by set membership rather than version comparison, and the reason is worth a paragraph. The public build table currently maps 219 firmware builds to compile timestamps, and every single one of them predates every CTX697096 fix floor. We verify that mechanically every time the template is regenerated. So a timestamp hit cannot resolve to a patched appliance, and there is no four-way cross-branch version comparison to get wrong. The 14.1, 13.1, 13.1-FIPS and NDcPP floors stop being a problem because we never compare versions at all.
id: netscaler-ctx697096-patch-gap
info:
name: Citrix NetScaler ADC / Gateway - CTX697096 Patch Gap (CVE-2026-88771, CVE-2026-88772)
author: xer0dayz
severity: critical
description: |
Builds below the CTX697096 fix floors are affected by CVE-2026-88771, an improper
input validation flaw allowing unauthenticated command execution, and
CVE-2026-88772, a DTLS memory overflow leading to RCE or DoS. Both were added to
CISA KEV on 2026-09-27 and both were exploited as zero-days before disclosure.
This template is NON-DESTRUCTIVE and PASSIVE. It issues a single GET for the static
resource /vpn/js/rdx/core/lang/rdx_en.json.gz and reads the firmware compile
timestamp from the gzip header MTIME field. It sends no command injection, no SAML
message, no DTLS record and no oversized buffer. It does not touch the login
endpoint.
remediation: |
Upgrade to 14.1-73.37, 13.1-64.23, 14.1-73.37 FIPS, or 13.1-37.279 (FIPS / NDcPP).
Branches 12.1 and 13.0 are end of life and have no fix; migrate them.
Disabling DTLS mitigates CVE-2026-88772 only. CVE-2026-88771 is independent.
reference:
- https://support.citrix.com/support-home/kbsearch/article?articleNumber=CTX697096
- https://nvd.nist.gov/vuln/detail/CVE-2026-88771
- https://nvd.nist.gov/vuln/detail/CVE-2026-88772
- https://github.com/fox-it/citrix-netscaler-triage
classification:
cvss-metrics: CVSS:4.0/AV:N/AC:L/AT:P/PR:N/UI:N/VC:H/VI:H/VA:H/SC:H/SI:H/SA:H
cvss-score: 9.5
cve-id: CVE-2026-88771
cwe-id: CWE-20
metadata:
verified: false
max-request: 2
vendor: citrix
product: netscaler_application_delivery_controller
patched-versions: 14.1-73.37,13.1-64.23,14.1-73.37-FIPS,13.1-37.279
kev: true
kev-date: 2026-09-27
shodan-query: title:"NetScaler Gateway"
tags: cve,cve2026,netscaler,citrix,rce,kev,patch-gap,version-detect,passive
http:
- method: GET
path:
- "{{BaseURL}}/vpn/js/rdx/core/lang/rdx_en.json.gz"
- "{{BaseURL}}/rdx_en.json.gz"
stop-at-first-match: true
redirects: false
matchers-condition: or
matchers:
# Supported branch below its fix floor. A fixed build exists.
- type: dsl
name: netscaler-known-vulnerable-build
dsl:
- "status_code == 200"
- "starts_with(hex_encode(body), '1f8b')"
- "gzip_mtime(body) > 0"
- "contains('|1690503901|1762655407| ...219 stamps... |',
concat('|', to_string(gzip_mtime(body)), '|'))"
condition: and
# 11.x / 12.x / 13.0: end of life, no fixed build exists at all.
- type: dsl
name: netscaler-eol-branch-no-fix-available
dsl:
- "status_code == 200"
- "starts_with(hex_encode(body), '1f8b')"
- "gzip_mtime(body) > 0"
- "contains('|1535167752|1762830097| ...EOL stamps... |',
concat('|', to_string(gzip_mtime(body)), '|'))"
condition: and
extractors:
- type: dsl
name: firmware_stamp
dsl:
- "concat('rdx_en_mtime=', to_string(gzip_mtime(body)), ' compiled=',
date_time('2006-01-02T15:04:05Z07:00', gzip_mtime(body)))"
Nuclei does the hard part for you here. gzip_mtime() is a first-class DSL function in the engine, so pulling a firmware compile time out of a gzip header is one expression rather than hand-rolled byte slicing. As far as we can tell nobody had pointed it at NetScaler before.
Save and run:
nuclei -t netscaler-ctx697096-patch-gap.yaml -u https://vpn.example.com
nuclei -t netscaler-ctx697096-patch-gap.yaml -l netscaler-hosts.txt -jsonl
A match looks like this:
[netscaler-ctx697096-patch-gap:netscaler-known-vulnerable-build] [http] [critical]
https://vpn.example.com/vpn/js/rdx/core/lang/rdx_en.json.gz
[rdx_en_mtime=1762655407 compiled=2025-11-08T21:30:07-05:00]
Pass that stamp to the bundled cve-2026-88771-detect.py and it resolves to an exact build, in this case 14.1-56.74, below the 14.1-73.37 floor.
Why this template is passive, and why that is not just caution
There is an obvious active check available. Plant a crash-diagnostic-shaped string in a login field, then look at whether the response reflects it unsanitized. An open community template upstream does roughly that.
We will not ship it, and the reason is specific rather than squeamish. An active check for this bug writes attacker-shaped text into the exact log file that incident responders need in order to establish prior compromise. This is an appliance CISA has flagged for forensic triage, exploited for three weeks before anyone had a patch, where the vendor’s own detection script only works if the logs have not yet rotated. Scanning it with a log-poisoning probe contaminates the primary evidence, burns log retention you may need, and makes your own scanner indistinguishable from the intrusion you are trying to rule out. On a fleet of a few hundred appliances you would be manufacturing a few hundred false leads for your own IR team.
A passive build check answers the same question with none of that.
Two blind spots, stated plainly
The build table ends on 11 November 2025. The newest builds it maps are 14.1-56.74 and 13.1-61.23. Anything released after that date is unmapped, which means a genuinely vulnerable 14.1-66.x through 14.1-73.36 appliance produces no finding from this template. Silence means undetermined. It does not mean patched. This is the honest cost of a false-positive-free design, and the companion fingerprint template plus the EPA package size oracle exist to narrow the gap. For anything you care about, confirm with show ns version.
Some firmware images are compressed with gzip -n, which zeroes the timestamp field. The matcher guard rejects a zero, so those hosts also report nothing. Same rule: undetermined, not clean.
A practitioner note while we are here, because it cost us some time: Nuclei’s date_time() does not use strftime tokens. In the % form, %m yields the minute and %M the month, and a bare Go layout such as 2006-01-02 15:04:05 is mangled into a run of digits. Only an offset-bearing layout like 2006-01-02T15:04:05Z07:00 renders correctly, and it renders in the scanning host’s local zone, not UTC. If you are comparing output against the Fox-IT table, which is published in UTC, use the raw epoch.
Detecting CVE-2026-88771 across your attack surface with Sn1per
A patch-gap template is only as good as your host list, and for edge appliances the host list is the actual problem. NetScaler appliances are bought by network teams, deployed for one application, inherited through acquisitions, and stood up as a disaster-recovery pair that nobody has logged into for two years. The ones that stay vulnerable are not the ones your vulnerability scanner knows about. They are the ones nobody remembered they had.
So treat it as a discovery problem before a scanning problem.
# Discover first: subdomains, certificate transparency, ports, services
sniper -t example.com -m recon -w citrix-audit
# Then the web pass, which fires the CTX697096 templates on every HTTPS port
sniper -t vpn.example.com -m web -w citrix-audit
# Sweep a known fleet
sniper -f netscaler-hosts.txt -m webscan -w citrix-audit
Both templates drop into /sniper/templates/nuclei/server/ and need no configuration change, because NUCLEI_TEMPLATES is a directory list that Nuclei recurses. Findings surface as P1 CRITICAL in the workspace, carrying the matcher name and the build stamp. Pull them out of a Professional or Enterprise install over the API:
curl -sk -H "X-API-Key: $SN1PER_API_KEY" \
"https://sn1per.local/pro/api.php?action=vulnerabilities&workspace=citrix-audit" \
| jq -r '.[] | select(.name | test("CTX697096")) | [.host, .severity, .details] | @tsv'
One more reason this particular job belongs on infrastructure you control. The output of this scan is a ranked list of your unpatched internet-facing VPN concentrators, during a window when a working exploit is public. That is not a document to hand to a multi-tenant SaaS scanner and hope about. Sn1per runs on your own hardware, in your own network, and the results stay there. We wrote about the general version of this argument in external attack surface management with Sn1per and compared the self-hosted options in open source self-hosted attack surface management tools.
Which Sn1per edition fits
| Capability | Community | Professional 2026 | Enterprise |
|---|---|---|---|
| Run the CTX697096 templates | Yes | Yes | Yes |
| Curated Nuclei set maintained for you | Partial | Yes | Yes |
| Workspaces and scan history | Basic | Yes | Yes |
| Web UI with asset risk ranking | No | Yes | Yes |
| JSON API for finding export | No | Yes | Yes |
| Scheduled recurring surface sweeps | No | Limited | Yes |
| Multi-user, delegated workspaces | No | No | Yes |
For a one-off answer to “are any of our NetScalers unpatched”, Community plus the template is enough. For the recurring version of that question, which is the one that actually matters given NetScaler has produced five KEV entries in 2026 alone, you want scheduling and history. There is more on that trade-off in our write-up of automated penetration testing tools.
Remediation and mitigation
- Upgrade to the CTX697096 builds now. 14.1-73.37, 13.1-64.23, 14.1-73.37 FIPS, or 13.1-37.279 for FIPS and NDcPP. The federal deadline was 30 September; there is no reading of this where waiting is reasonable.
- Migrate 12.1 and 13.0 appliances. There is no patch. A CVSS 9.5 with no vendor fix path on an internet-facing VPN concentrator is an architectural problem, not a patching one, and it should sit above the merely-unpatched boxes in your queue.
- Patch both HA nodes. Check the standby by address, not through the VIP.
- Assume breach, and do it before you reboot into the new image. Exploitation ran for roughly three weeks before a fix existed. Preserve the appliance logs and a configuration snapshot first. Citrix offers IOC scanning through NetScaler Console on 14.1-73.36 or later with telemetry enabled, but states its own IOC set does not cover every known technique, and the vendor script depends on logs that may already have rotated. A clean IOC scan is weak evidence.
- Hunt for the known artifacts. The web shell path
/var/netscaler/logon/LogonPoint/custom/.ctxs.receiverand its SHA256, unexpectedhttpd.confmodifications, stray archives under the GUI web root, and DTLS handshake failures followed by a packet engine crash. Remember the 404-returning web shell variants and the up-to-24-hour delay between injection and execution, which means your log review window needs to start well before the first suspicious execution. - Rotate everything the appliance held. TLS private keys, service account credentials in
ns.conf, LDAP and RADIUS bind passwords, session signing material. If the configuration archive was exfiltrated, all of it is burned. Patching does not revoke a credential. - Reduce the surface afterwards. Management interfaces should not be internet-facing. Turn DTLS off where no client needs it, understanding that this narrows CVE-2026-88772 only.
Frequently asked questions
What is CVE-2026-88771?
It is an improper input validation vulnerability, CWE-20, in Citrix NetScaler ADC and NetScaler Gateway that lets an unauthenticated attacker run arbitrary commands as root. The attacker gets their text into an appliance log, and a Perl crash-handling script later reads that text back and interpolates it into a shell command. Citrix scores it CVSSv4 9.5 and NVD scores it CVSS 3.1 9.8. It affects the default configuration, so no special feature needs to be enabled.
Which NetScaler versions are affected by CVE-2026-88771 and CVE-2026-88772?
NetScaler ADC and Gateway before 14.1-73.37, before 13.1-64.23, before 14.1-73.37 FIPS, and before 13.1-37.279 for the FIPS and NDcPP lines. Branches 12.1 and 13.0 are end of life and get no fix for either CVE. Patching for the earlier CVE-2026-19490 does not cover these.
Are CVE-2026-88771 and CVE-2026-88772 being exploited in the wild?
Yes. Both were exploited as zero-days before Citrix published CTX697096, and CISA added both to the KEV catalog on 27 September 2026 with a three-day remediation deadline. Google Threat Intelligence Group and Mandiant date the campaign to early September, affecting government, financial services, education, legal and professional services organizations in North America and Europe. A public proof of concept for CVE-2026-88771 appeared on 27 September.
How do I detect CVE-2026-88771 remotely if NetScaler does not report its version?
Read the firmware compile timestamp out of the gzip header of /vpn/js/rdx/core/lang/rdx_en.json.gz, which maps one to one onto a published build. The Nuclei template above does this with the engine’s native gzip_mtime() function, and the bundled Python script resolves the timestamp to an exact build string. Be aware the public build table ends in November 2025, so a newer appliance will come back undetermined rather than clean.
Is disabling DTLS enough to fix this?
No, and this is the most common mistake being made with this bulletin. Disabling DTLS removes exposure to CVE-2026-88772 only. CVE-2026-88771 needs no DTLS and remains fully exploitable against an unpatched appliance in its default configuration. Only the firmware upgrade addresses both.
Is the Nuclei template safe to run against production appliances?
Yes. It sends two plain GET requests for static files and reads metadata from the response. No command injection, no SAML message, no DTLS record, no oversized buffer, and it never touches the login endpoint. We deliberately avoided the available active check because it would write attacker-shaped strings into the same logs your incident responders need, on appliances flagged for forensic triage.
Closing
The interesting thing about CVE-2026-88771 is not that an edge appliance had a command injection. It is that the delivery vehicle was a log file. The vulnerable code was never reachable directly, the entry point was any one of a dozen unrelated pre-auth paths, and execution happened up to a day later. Signature-based detection struggles with all three properties. Knowing precisely what firmware every appliance on your perimeter is running does not struggle with any of them, which is why a boring build inventory keeps outperforming clever detection on exactly this class of bug.
NetScaler has produced five CISA KEV entries in the first nine months of 2026. There will be a sixth. The build fingerprint template is the part of this work that keeps paying, because it answers the next advisory too.
Grab the templates, run them across every HTTPS port you own rather than just the hosts you remember, and if you find an appliance below the floor, preserve the evidence before you reboot it.