Skip to content
KitploitKITPLOIT
ToolsBlog
Log in
Submit
ToolsBlog
Submit

Hacking, PenTest, and Cybersecurity Tools for Your Security Arsenal!

Kitploit is a directory of hacking, cybersecurity, and pentesting tools. Discover the latest project updates to find vulnerabilities, analyze systems, automate testing, and strengthen your security.

FeedsContactPrivacy© 2026 Kitploit

Tool Directory

Categories

View all categories
Loading categories
CVE-2026-19490-check — Safely detect Citrix NetScaler SAML auth bypass CVE-2026-19490 | Kitploit
Tools/GitHubGitHub/bishopfox/cve-2026-19490-check
Defensive ToolsVulnerability ScannersWeb Vulnerability ScannersVulnerability AnalysisInformation GatheringWeb SecurityNetwork SecurityPenetration TestingAuthentication
GitHubbishopfox/cve-2026-19490-check

CVE-2026-19490-check

122621 days agoNot yet reviewed

Most Popular

View all →

Discover the most used tools by our community.

Explore all tools

Browse our collection of tools

View all tools →

Safely detect Citrix NetScaler SAML auth bypass CVE-2026-19490

View Repository
Share

Citrix NetScaler SAML nFactor Context Authentication Bypass — Vulnerability Detection Script

A safe, unauthenticated vulnerability check for CVE-2026-19490, the pre-authentication authentication bypass in the Citrix NetScaler ADC / NetScaler Gateway SAML service-provider path (CTX696939, published 2026-08-19). CWE-288, CVSS v4.0 9.3 (AV:N/AC:L/AT:N/PR:N/UI:N/VC:H/VI:H/VA:H/SC:L/SI:L/SA:L). Reported by Samarth Vashisht of the JPMorgan Chase penetration testing team.

The appliance base64-decodes the RelayState parameter of a SAML response and, when the plaintext begins with ctx=, hands the remainder to the nFactor context deserializer. On an unpatched build a deserialization failure propagates the decoded RelayState length as the request's internal disposition code instead of an error, so an unauthenticated attacker chooses which internal branch the appliance takes next purely by choosing how long the RelayState is. Some branches mint a real Gateway session; others crash the packet engine and restart the appliance. This script does neither — it sends the one length validated to create no session and leave the packet engine alone, and answers a single question per target: is this appliance vulnerable? A result other than VULNERABLE is not by itself a clean bill of health.

Usage

# single target
./cve_2026_19490_check.py https://gateway.example.com

# a specific Gateway or AAA virtual server
./cve_2026_19490_check.py https://gateway.example.com:9443

# several targets; the scheme defaults to https://
./cve_2026_19490_check.py gw-a.example.com gw-b.example.com:9443

# scan a list, one target per line ('#' comments allowed), compact output
./cve_2026_19490_check.py -f targets.txt --brief

# machine-readable output for pipelines
./cve_2026_19490_check.py -f targets.txt --json > results.json

Python 3.8+, standard library only — no third-party packages.

Point the tool at the Gateway or AAA virtual server, not the management interface. Exposure is per virtual server, so an appliance with several VIPs needs each one tested. The probe carries a hard safety envelope — one validated RelayState length, never swept — which Is it Safe to Run? sets out.

Options

FlagDescription
TARGETOne or more [https://]HOST[:PORT] targets; the scheme defaults to https://
-f, --targets-file FILERead targets from a file (one per line; # comments)
--timeout SECSPer-request timeout (default: 15)
--workers NConcurrent targets (default: 16); output stays in input order
-b, --briefSingle aligned line per target — ideal for scanning many hosts
--jsonEmit structured JSON, including every request sent per target
--no-colorDisable coloured output (also honours NO_COLOR and non-TTY)

Examples

A vulnerable appliance (the default two-line output). The [!] marker and VULNERABLE render red on a TTY:

$ ./cve_2026_19490_check.py https://gateway.example.com:9443
[!] https://gateway.example.com:9443: VULNERABLE  [internal-error-43524]
      HTTP 500 / 43524: the decoded RelayState length was propagated as the internal error, so the CTX696939 fix is absent

A patched appliance:

$ ./cve_2026_19490_check.py https://vpn.example.com
[+] https://vpn.example.com: PATCHED  [fixed-error-returned]
      HTTP 200 "Malformed Assertion": the fixed error was returned on the path this probe reached, so the CTX696939 fix is present (>= 13.1-63.21 / 14.1-73.32)

The false-positive guard firing. The probe and the same-length control both returned the unpatched signal, so the reply does not depend on what was sent and the decisive-looking answer is withdrawn:

$ ./cve_2026_19490_check.py https://sp-strict.example.com
[?] https://sp-strict.example.com: INCONCLUSIVE  [flat-response]
      the probe and the same-length control both answered HTTP 500 / 43524, so the reply does not depend on what was sent and the fix was never exercised; unknown, not patched

Sweeping an estate (--brief). The two gateway.example.com rows are the SP and IdP-only virtual servers on the same appliance — both answer, which a configuration-precondition check would not manage:

$ ./cve_2026_19490_check.py -f targets.txt --brief; echo "exit: $?"
VULNERABLE    https://gateway.example.com:9443         internal-error-43524
VULNERABLE    https://gateway.example.com:9444         internal-error-43524
PATCHED       https://vpn.example.com                  fixed-error-returned
INCONCLUSIVE  https://sp-strict.example.com            flat-response
UNAFFECTED    https://lb.example.com                   no-saml-endpoint
ERROR         https://www.example.com                  not-identified
exit: 1

Machine-readable output (--json). Every request is included, so a finding can be re-derived from the evidence rather than trusted. The control is recorded by its relation to the probe rather than as a verdict of its own, because a control that reads like a patched build is the expected result on every build:

$ ./cve_2026_19490_check.py https://gateway.example.com:9443 --json
[
  {
    "target": "https://gateway.example.com:9443",
    "verdict": "VULNERABLE",
    "reason": "internal-error-43524",
    "detail": "HTTP 500 / 43524: the decoded RelayState length was propagated as the internal error, so the CTX696939 fix is absent",
    "netscaler_indicators": [
      "CSP contains citrixng://",
      "CSP contains com.citrix.nsgclient://",
      "CSP contains nsgcepa://",
      "CSP report-uri /nscsp_violation/report_uri",
      "/vpn/js/rdx/ present (HTTP 404)"
    ],
    "attempts": [
      {
        "kind": "probe",
        "path": "/cgi/samlauth",
        "status": 500,
        "state": "unpatched",
        "detail": "HTTP 500 / 43524: the decoded RelayState length was propagated as the internal error"
      },
      {
        "kind": "control",
        "path": "/cgi/samlauth",
        "status": 200,
        "state": "differs-from-probe",
        "detail": "same-length control: HTTP 200 \"Malformed Assertion\": the fixed error was returned"
      }
    ]
  }
]

Is it Safe to Run?

Yes. It is designed for production and assessment use:

  • One fixed length, never a sweep. The probe's RelayState decodes to exactly 20 bytes and the tool sends no other length under any flag. On an unpatched appliance the decoded length is the branch selector, and the branches include ones that create a session and ones that SIGSEGV the packet engine — restarting the whole NetScaler and dropping traffic for roughly 45 seconds. Twenty bytes lands on a clean internal-error branch that creates no session, and the payload builder refuses to construct any other length, so an edit elsewhere cannot quietly widen the probe.
  • Validated by measurement. The 20-byte probe was run 10 times consecutively against an unpatched 13.1-63.18 with zero cores generated and no packet-engine restart. The check itself has since been run against both maintenance branches, on both sides of the fix, and against service-provider and IdP-only virtual servers alike, with no packet-engine restart on any of them.
  • No authentication, no credential material, no state change. No assertion, signature, timestamp, session, or client certificate is presented anywhere in this check. The SAMLResponse parameter must be present for the RelayState branch to be reached, but its content is irrelevant, so it is four bytes of junk. The branch issues no cookie and writes no configuration.
  • False-positive guard. VULNERABLE is never reported on a single response — see the control.
Download Tool