Mind the Config: Detecting and Weaponizing NetScaler CVE-2026-19490

Mind the Config: Detecting and Weaponizing NetScaler CVE-2026-19490

Share

TL;DR
CVE-2026-19490 is an authentication bypass in the SAML handling on Citrix NetScaler ADC and Gateway, rated CVSS 9.3. A single unauthenticated request makes the appliance run its post-login code, but what that is worth depends entirely on configuration: from a reliable pre-authentication crash, through a proxy into the internal network, up to root on the appliance. Patch to 13.1-63.21 or 14.1-73.32 or later (12.1 and 13.0 are end of life). Patch state is measurable from outside in one safe request, which we published as a detection tool. Read on for a branch-by-branch map from that safe check up to root command execution.

Summary

On August 19, 2026, Citrix published CTX696939, covering two vulnerabilities in NetScaler ADC and Gateway. CVE-2026-19490 is the more severe of the pair, a CWE-288 authentication bypass scoring CVSS 9.3 (Critical), described by the vendor as an “authentication bypass using an alternate path” reachable on a Gateway or AAA virtual server. Credit for the original report goes to Samarth Vashisht of the JPMorgan Chase penetration testing team.

We came at it from the defender’s side. Diffing the packet engine binary across the fix showed the whole change in the affected region is a single instruction: the vulnerable build moves the decoded length into the status register, and the fixed build moves a constant error code instead. From there we mapped how far the bypass actually goes against a lab of NetScaler VPX appliances, and built a detection method that answers the patch-state question in one request without touching any of the branches that crash the box. This post walks that map in order: how to find the first link safely, then each configuration fork that decides what an attacker can do once the link is confirmed.

Our detection tool can be found at github.com/BishopFox/CVE-2026-19490-check.

What Defenders Should Do Right Now

  • Patch. The fix ships in 13.1-63.21 and 14.1-73.32, which are the minimum builds that close it; FIPS and NDcPP fleets need 14.1-73.32 FIPS, 13.1-37.277 for 13.1-FIPS and 13.1-NDcPP. Go to the latest available build on your branch. On 12.1 or 13.0 no fix is coming, so migrate. Patch both nodes of an HA pair, since an unpatched secondary is fully exposed the moment it takes over.
  • Scope by SAML configuration. Check the running config for add authentication samlAction and add authentication samlIdPProfile alongside add authentication vserver and add vpn vserver. show ns runningConfig | grep -i saml collapses that into one command. Test each virtual IP address (VIP) separately, since exposure is per virtual server.
  • If you cannot patch today, check the one global knob that bounds the worst case. The session this bug mints on a Gateway skips session-policy evaluation, so it does not pick up any per-vserver authorization setting; it falls back to the global set vpn parameter -defaultAuthorizationAction. The shipped default is DENY, but ALLOW is common in the field because it saves writing per-user policies, and ALLOW is what lets the unauthenticated session reach internal resources. Setting it to DENY bounds the impact, but don’t rely on it as a fix.
  • Verify rather than assume. Run show ns version where you have credentials, and our safe probe against the Gateway or AAA virtual server (not the management interface) where you do not.

Background

NetScaler ADC and Gateway sit at the network edge, terminating SSL VPN, ICA proxy, clientless VPN, and RDP proxy sessions for the network behind them. They are internet facing by design and trusted by the internal systems they front, which is what makes a pre-authentication bug here worth chasing all the way down.

When NetScaler acts as a SAML Service Provider, it consumes assertions from an external identity provider at /cgi/samlauth. A SAML response can carry a RelayState parameter, normally an opaque round-trip value the appliance hands back at the end of a login. This appliance does something more with it: it base64-decodes RelayState, and if the decoded bytes begin with ctx=, it treats the remainder as a serialized nFactor authentication context. nFactor is NetScaler’s multi-step login framework, so that context is the appliance’s own saved state for a login in progress, packed into bytes so it can ride along in the request. NetScaler hands the blob to an internal deserializer to unpack and resume that login. That deserializer is where the bug lives, and reaching it needs nothing but a RelayState that decodes to the right four-byte prefix.

The Vulnerability

The nFactor context is cryptographically protected. The deserializer checks a signature and a hash over it before any field is used, so attacker-supplied context data always fails verification. The bug is in what the appliance does with that failure.

On an unpatched build, when verification fails, the SAML response handler carries the decoded RelayState length forward as the request’s internal disposition code, then dispatches on it. The length becomes a small integer that selects which internal branch runs next, and the attacker sets it by choosing how many bytes the RelayState decodes to. The fix replaces that length with a fixed error constant, so every failed context returns the same “malformed” answer regardless of size.

Because the length is the selector, the length is the payload. Different lengths land on very different branches:

Decoded length

Branch on an unpatched build

18, 21

Packet engine crashes and restarts in place, dropping all traffic for ~45s

12, 20

Clean internal error, no session created (the safe detection point)

11, 16

Reaches post-login code: “created session for anonymous”

22, 23

Falls through into client-certificate authentication

The map is identical on both vulnerable branches (13.1-63.18 and 14.1-73.30), so which branch a length hits is a property of the bug, not the build. A patched build answers “malformed” at every length, and that difference is what the detection below keys on. The safe length, 20, sits between the two crash lengths (18 and 21), which is why the check fixes one length and never sweeps.

Always Anonymous

One limit shapes everything downstream, so it comes before the impact walk. Because the context is authenticated with a secret only the appliance holds, an attacker cannot forge it into a chosen user. Every session this bug mints is anonymous. That caps the attacker’s identity. It does not cap the access an anonymous session can reach, which is where the severity comes from.

Preconditions: When a Target Is Actually Reachable

On current builds, the deserializer is only reachable where a SAML action is configured on the virtual server being addressed. An appliance with no SAML configuration answers 404 on /cgi/samlauth, and no length reaches the vulnerable code. This is the first fork in the tree below, and it is why the inventory step scopes by SAML rather than by virtual server type.

One older-build caveat, taken from the advisory rather than measured: on 14.1-43.55 and 13.1-61.27 and earlier, exposure does not require a SAML action at all, and any Gateway or AAA virtual server is affected. On those builds, the absence of a SAML action does not put an appliance out of scope.

Detecting It Safely

The check behaves like a yes-or-no question. Send one ordinary-looking SAML POST whose RelayState decodes to the safe 20-byte length, and read the answer:

Response to the probe

Build

Verdict

500 carrying internal error 43524

unpatched

VULNERABLE

200 "Malformed Assertion"

patched

PATCHED

The unpatched build propagates the decoded length as its error and returns 43524; the patched build returns the fixed error at every length, so Malformed Assertion is what it always says. SAMLResponse has to be present for the RelayState branch to run, but its content is irrelevant, so the probe is four bytes of junk. No assertion, signature, timestamp, session, or client certificate is involved, which is what makes it a clean pre-authentication check.

A single 500 is not enough to call something vulnerable on its own, so after the unpatched signal the tool repeats the request with a control RelayState of the same 20-byte length, differing only in the bytes after ctx=. That content is rejected by a guard ahead of the deserializer, so every build answers it 200 Malformed Assertion. The verdict stands only if the probe and the control differ. If they match, something is returning 500/43524 regardless of input, and the result is INCONCLUSIVE rather than a false VULNERABLE. Holding the control at the same length keeps it inside the safe envelope and isolates content as the only variable.

Our detection tool reports five verdicts, and the distinction between them is the point:

Verdict

Meaning

VULNERABLE

The fix is absent and the same-length control proved the reply depends on what was sent. Patch it.

PATCHED

The fixed error was returned on the path the probe reached. Scoped to this CVE and to that path.

UNAFFECTED

No SAML endpoint answered here, so the bug is not reachable through this virtual server.

INCONCLUSIVE

A response came back, but it does not let the tool separate patched from vulnerable. Unknown, not patched.

ERROR

Not identified as a NetScaler, or unreachable.

VULNERABLE is the only self-corroborating answer, so a non-match is never a clean bill of health. Because the tool exits 0 for everything that is not VULNERABLE, a wrapper that needs to tell “patched” from “could not be classified” has to read the verdict, not the exit code. The probe also answers on Identity-Provider-only virtual servers, because RelayState deserialization runs before SAML policy matching, so it covers more surface than a configuration-precondition check can. At scale, point it at whatever URL reaches the Gateway or AAA virtual server rather than the management interface, and confirm any non-match against the fix builds with show ns version.

You Are Vulnerable. Now What?

A VULNERABLE verdict confirms the first link. It does not, by itself, tell you what an attacker can do with the appliance, because that is decided by a short series of configuration questions. The figure below is the whole decision tree; the rest of this section walks it.

Figure 1: Impact Decision Tree
Figure 1: Impact Decision Tree

Decision tree from the safe patch-state check down through each configuration fork to root on the appliance. Teal outcomes are dead ends or bounded, amber are partial, and red are the severe cases, deepening to solid red at root on the appliance.

Denial of Service, on Any Vulnerable Box

One outcome is available no matter how the appliance is configured: denial of service. Independent of every fork below, a crash-length POST takes down the packet engine that carries all traffic through the appliance, so it denies service to every virtual server on the box for roughly 45 seconds while it restarts. Sent repeatedly it gets worse: pitboss, the appliance’s process supervisor, treats a packet engine that keeps dying as a system failure and reboots the whole box. In our lab, three crashes inside about ninety seconds escalated a series of brief restarts into a full appliance reboot.

This works on any vulnerable appliance regardless of virtual server type or configuration, and denial of service is the one outcome an attacker can guarantee.

Fork 1: Which Virtual Server Type?

The interesting branches start with the length-16 request that reaches post-login code. The first fork is the virtual server type.

On an AAA virtual server, the bypass is bounded. The appliance creates an anonymous server-side session but returns no usable session cookie, so nothing behind it becomes reachable. This is a real authentication-decision bypass, but a dead end for access.

On a Gateway (vpn vserver), the same request returns a real NSC_AAAC session cookie, and the appliance logs an SSL VPN login for user anonymous in clientless mode. The session then walks the appliance’s own post-login redirect chain, fetching /cgi/setclient?cvpn and then the clientless VPN landing page in that order, and becomes an HTTP(S) proxy into the internal network through the clientless VPN path.

Fork 2: The Global Authorization Action

That Gateway proxy can facilitate a server-side request forgery from the appliance’s own network position, and its configuration is the next fork: the global -defaultAuthorizationAction. The anonymous session never goes through session-policy evaluation, so it falls back to that global setting. On DENY, the session exists but the proxy refuses, and the impact stays bounded. On ALLOW, the proxy serves internal content, and this is where the severity lives.

With the proxy serving, the unauthenticated session becomes a general HTTP(S) request forgery originating from inside the network perimeter. It fetches over both http and https, on any port, from any host the appliance can route to, not just the bookmarks an administrator published. What that is worth is, once again, a function of configuration, this time the internal network’s: the bug hands an outside attacker a client that already sits inside, and everything reachable from the appliance’s position is in scope.

In practice that means internal web applications and intranet sites that were never meant to face the internet; the web management interfaces of other appliances, firewalls, hypervisors, and network gear; internal dashboards and APIs that authorize by network location rather than by credentials; and the appliance’s own NSIP and NITRO management API, normally firewalled off from the internet but reachable now that the request originates inside. On a flat internal network with unauthenticated admin panels, that is a foothold for deep internal compromise. On a well-segmented one it is far more contained. In the lab the session retrieved internal hosts that were never published and reached both the appliance’s own and peer appliances’ management login pages and NITRO API, which is the pivot to the credentialed path below.

There are only two limits, and both are narrow: it is HTTP(S) only, so non-HTTP services such as the RPC port are out of reach and there is no raw-socket primitive, and loopback (127.0.0.1, ::1) is refused at the proxy.

Fork 3: A StoreFront Behind the Gateway?

If a StoreFront is published behind the Gateway (ICA proxy mode), the anonymous session can be placed into ICA/Web mode and driven into the StoreFront enumeration path through the Gateway. We proved the Gateway-side half of this: the Gateway forwards the unauthenticated session into that path without imposing its own gate. Whether a real StoreFront then returns published apps and desktops depends on the store’s own identity check, and the session’s identity is the unforgeable anonymous established above. A standard domain-authenticated store, asked “who is this user,” most likely returns nothing; the enumeration succeeds mainly against unauthenticated or loosely trusting stores. In our lab, we were able to list and control available apps and desktops within a vulnerable Gateway appliance, but we did not validate this against production Citrix infrastructure.

Fork 4: Management Credentials in Reach?

Default, reused, or phished credentials turn the SSRF into full appliance control. The tool logs in to NITRO through the proxied channel and relays the configuration API end to end, reading and writing the appliance’s config. From there, root on the appliance is a short step through cron: NITRO’s file interface appends a one-shot line to the system crontab and cron runs it as root within about a minute. We confirmed root command execution end to end on 13.1-63.18. This rung is credentialed, so it is not part of the unauthenticated chain, but the position matters: the SSRF turns a stolen or default management password into root on a box that password could never have reached from the outside.

The whole point of the tree is that “vulnerable” is the entry condition, not the impact. The same bug is a dead-end session on one appliance, a full-appliance-outage lever on the next, and an unauthenticated pivot into the internal network (and, with a weak management password, root) on a third, decided entirely by the virtual server type and two configuration settings.

The Patch

The fix, in 13.1-63.21 and 14.1-73.32, is one instruction. On a failing context deserialization the vulnerable build carries the decoded RelayState length forward as the request’s status; the fixed build carries the constant error 0xe0005, rendered as Malformed Assertion. That one change collapses the whole attacker-controlled branch selector into a single fixed answer, which is why a patched appliance says the same thing at every length.

Patching genuinely closes the bypass. It does not change the configuration that decides the blast radius on an unpatched box, which is why the -defaultAuthorizationAction note above is defense in depth for the window before you patch, and a good posture to keep afterward.

Conclusion

CVE-2026-19490 is a pre-authentication authentication bypass an attacker reaches in a single unauthenticated request against a NetScaler Gateway or AAA virtual server with SAML configured. Upgrade to 13.1-63.21 or 14.1-73.32 or later, remembering FIPS and NDcPP move on their own cadence, and that 12.1 and 13.0 have no fix coming.

The reason to read past the CVSS number is that the score is a ceiling, not a description of your box. Detection tells you whether you are standing under it: one safe request, per virtual server, with a non-match treated as unknown rather than clean. What sits above that first link is a short chain of configuration questions. An AAA virtual server bounds the bug to a dead-end anonymous session. A Gateway turns it into a real session, and the global -defaultAuthorizationAction decides whether that session proxies into the internal network and the appliance’s own management plane. A StoreFront behind the Gateway opens a published-application enumeration path whose payoff depends on the store’s identity checks, and a weak management password turns the whole thing into root on the appliance. The bug never changes; the configuration decides how far down the chain it gets.

Cosmos customers were notified about this vulnerability research shortly after the vendor advisory published. If you are interested in learning more about managed services delivered through our Cosmos platform, visit bishopfox.com/services/continuous-threat-exposure-management.

Our detection tool is available on GitHub: github.com/BishopFox/CVE-2026-19490-check.

For more vulnerability intelligence insights, visit the Bishop Fox Blog.


Jon Williams

By Jon Williams

Staff Security Engineer

As a researcher for the Bishop Fox Threat Enablement & Analysis team, Jon spends his time hunting for vulnerabilities and writing exploits for software on our customers' attack surface. Jon has written and presented research on various topics including enterprise wireless network attacks, bypassing network access controls, and reverse-engineering edge security device firmware.


Banksy Fox exploder1

By Threat Enablement & Analysis Team

The Bishop Fox Threat Enablement & Analysis team researches emerging vulnerabilities, exploits, and attacker techniques to understand how new threats translate into real-world risk. The team combines vulnerability research, exploit development, threat intelligence, and offensive security expertise to analyze new disclosures, validate exploitability, and develop methods for identifying affected systems at scale.

Subscribe to our blog

Be first to learn about latest tools, advisories, and findings.