
Rileva in modo sicuro il bypass di autenticazione SAML di Citrix NetScaler CVE-2026-19490
Un controllo di vulnerabilità sicuro e non autenticato per CVE-2026-19490, il bypass di
autenticazione pre-autenticazione nel percorso del service provider SAML di Citrix NetScaler ADC / NetScaler Gateway
(CTX696939,
pubblicato il 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). Segnalato da Samarth Vashisht del
team di penetration testing di JPMorgan Chase.
L'appliance decodifica in base64 il parametro RelayState di una risposta SAML e, quando il testo in chiaro
inizia con ctx=, passa il resto al deserializzatore del contesto nFactor. Su una build non patchata un
fallimento di deserializzazione propaga la lunghezza del RelayState decodificato come codice di
disposizione interno della richiesta invece di un errore, così un attaccante non autenticato sceglie quale ramo
interno l'appliance prende successivamente semplicemente scegliendo quanto è lungo il RelayState. Alcuni rami generano una vera
sessione Gateway; altri mandano in crash il packet engine e riavviano l'appliance. Questo script non fa né l'una né l'altra
cosa — invia l'unica lunghezza validata per non creare alcuna sessione e non toccare il packet engine, e
risponde a una sola domanda per target: questa appliance è vulnerabile? Un risultato diverso da
VULNERABLE non è di per sé un certificato di buona salute.
# 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+, solo libreria standard — nessun pacchetto di terze parti.
Punta lo strumento al Gateway o al virtual server AAA, non all'interfaccia di gestione. L'esposizione è
per virtual server, quindi un'appliance con diversi VIP richiede che ciascuno venga testato. La sonda porta con sé un
rigido involucro di sicurezza — un'unica lunghezza RelayState validata, mai scansionata a tappeto — che
È Sicuro Eseguirlo? illustra.
| Flag | Descrizione |
|---|---|
TARGET | Uno o più target [https://]HOST[:PORT]; lo schema predefinito è https:// |
-f, --targets-file FILE | Legge i target da un file (uno per riga; commenti #) |
--timeout SECS | Timeout per richiesta (predefinito: 15) |
--workers N | Target concorrenti (predefinito: 16); l'output resta nell'ordine di input |
-b, --brief | Una singola riga allineata per target — ideale per scansionare molti host |
--json | Emette JSON strutturato, includendo ogni richiesta inviata per target |
--no-color | Disabilita l'output colorato (rispetta anche NO_COLOR e non-TTY) |
Un'appliance vulnerabile (l'output predefinito su due righe). Il marcatore [!] e VULNERABLE vengono
resi in rosso su un 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
Un'appliance patchata:
$ ./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)
La guardia anti-falsi positivi che scatta. La sonda e il controllo della stessa lunghezza hanno entrambi restituito il segnale non patchato, quindi la risposta non dipende da ciò che è stato inviato e la risposta dall'aspetto decisivo viene ritirata:
$ ./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
Scansione di un parco macchine (--brief). Le due righe gateway.example.com sono i virtual server SP e
solo-IdP sulla stessa appliance — entrambi rispondono, cosa che un controllo di precondizione di configurazione non
riuscirebbe a fare:
$ ./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
Output leggibile dalla macchina (--json). Ogni richiesta è inclusa, così un risultato può essere ri-derivato
dalle prove anziché essere preso per buono. Il controllo è registrato per la sua relazione con la sonda
piuttosto che come un verdetto a sé stante, perché un controllo che si legge come una build patchata è il risultato atteso
su ogni 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"
}
]
}
]
Sì. È progettato per l'uso in produzione e in fase di assessment: