
Behavioral patch-state detector for Citrix NetScaler CVE-2026-8452. Sends crafted SAML requests to determine whether the PrefixList size check is present, without exploiting or corrupting memory.
A safe, non-destructive patch-state check for CVE-2026-8452, the pre-authentication heap overflow
in the Citrix NetScaler ADC / NetScaler Gateway SAML signature canonicalizer
(CTX696604,
CVSS 8.8). An oversized exclusive-canonicalization PrefixList overflows a fixed-size buffer during
canonicalization, which NetScaler performs before validating the signature that carries it — so the
whole path is reachable with no credentials, no session, and no valid signature. Reported by Michael
Tucker of the JPMorgan Chase XOR team; root-cause and exploitation analysis credit to
watchTowr Labs.
This script does not exploit the bug and does not corrupt memory. It answers one question per target: is the fix present on this appliance? — determined behaviourally, by observing the patch rather than guessing the build.
Yes. It is designed for production and assessment use:
PrefixList of 512 bytes and reject 513 or more. That limit was located to the byte,
is identical on both supported branches, and does not move with the appliance's configuration or
with the shape of the surrounding SAML message — confirmed by probing both routes, which wrap the
value in substantially different amounts of XML, and finding they change behaviour at the same
byte. 575 clears the limit by 63 bytes, so the verdict does not depend on how a target happens to
be set up.PrefixList attribute specifically. Inflating other fields past it — assertion consumer service URLs,
issuer names, algorithm identifiers, digest and signature values — changes nothing on a fixed build, so
applying the fix should not cause a working SAML configuration to start failing.If you modify the probe, do not change
PROBE_PREFIXESand do not sweep lengths. 575 bytes is load-bearing. OtherPrefixListlengths can destabilize an appliance, in at least one case on a build that carries this fix, so a length sweep is not a safe way to explore this bug and shorter is not safer.
Patched builds reject an oversized PrefixList cleanly, with a distinctive message. Unpatched builds
fall through the parser and return a generic internal error. One identical request, two different
answers:
575-byte PrefixList | Response |
|---|---|
| Unpatched | 500 Internal Server Error 43549 |
| Patched | 200 Malformed Assertion sent to Netscaler |
Two routes are tried, IdP first, stopping as soon as one gives an answer. Either is sufficient alone, and together they cover both SAML roles:
| Route | Request | Requires |
|---|---|---|
| 1 (first) | POST /saml/login — signed AuthnRequest, PrefixList in ds:SignedInfo | a SAML IdP policy bound to the targeted vserver |
| 2 (fallback) | POST /cgi/samlauth — SAMLResponse, PrefixList in the assertion signature | a SAML SP assertion consumer service on the targeted vserver |
The IdP route goes first because it is the more robust of the two. It is insensitive to the Issuer
value, to the AssertionConsumerServiceURL, and to clock skew — an IssueInstant well outside the
appliance's skew tolerance still discriminates correctly, because canonicalization precedes the time
check as well as the signature check.
The route-1
AuthnRequestmust be signed. An unsigned one returns200 Malformed Assertion sent to Netscaleron patched and unpatched builds, which is byte-identical to the patched signal, so a probe that omits the signature block reports every appliance as patched. The signature does not need to be valid, and this tool's is not; it only has to be present, because itsSignedInfois what carries thePrefixListinto the canonicalizer.
Both supported branches change behaviour exactly at their fix build, on both routes:
| Build | Verdict | |
|---|---|---|
13.1-63.16 | last vulnerable 13.1 | VULNERABLE |
13.1-63.18 | first fixed 13.1 | PATCHED |
14.1-66.59 | vulnerable 14.1 | VULNERABLE |
14.1-72.61 | first fixed 14.1 | PATCHED |
13.1-63.16 and 63.18 are consecutive releases, so the change is attributable to the patch itself
rather than to drift across intervening builds.
Those are the builds where this fix first appeared, and the probe detects exactly that transition.
They are no longer the builds to upgrade to: later bulletins have superseded them, so 13.1-63.18 and
14.1-72.61 both answer PATCHED here while remaining exposed to newer issues. See
Remediation for the current fixed builds.
Because it cannot work on this bug, even in principle. 13.1-63.16 and 13.1-63.18, the builds
immediately either side of the fix, serve byte-identical tmindex.html, base.css and
resources.js — the fix touches no web asset. Static asset hashes also collide across branches, so a
hash-based approach can resolve a vulnerable appliance to a patched build and report it as clean,
which is the worst failure mode a detection tool has. Build fingerprinting is therefore deliberately
not implemented. Patch state comes from the probe, or from show ns version where you have
credentials.
# single target
./cve_2026_8452_check.py https://gateway.example.com
# a specific AAA / Gateway virtual server
./cve_2026_8452_check.py https://gateway.example.com:9443
# scan a list, one target per line ('#' comments allowed), compact output
./cve_2026_8452_check.py -f targets.txt --brief
# machine-readable output for pipelines
./cve_2026_8452_check.py -f targets.txt --json > results.json
Point the tool at the Gateway or AAA virtual server, not the management interface. The precondition is per virtual server, so an appliance with several VIPs needs each one tested.
| Flag | Description |
|---|---|
URL | One or more https://HOST[:PORT] targets |
-f, --targets-file FILE | Read targets from a file (one per line; # comments) |
-b, --brief | Single aligned line per target — verdict, target, reason tag — for scanning many hosts |
--json | Emit structured JSON results |
--no-color | Disable coloured output (also honours NO_COLOR and non-TTY) |
--timeout SECS | Per-request timeout (default: 15) |
An unpatched appliance, answered on the IdP route and confirmed against the control: