
Détecter en toute sécurité le contournement d'authentification SAML de Citrix NetScaler CVE-2026-19490
Une vérification de vulnérabilité sûre et non authentifiée pour CVE-2026-19490, le contournement
d'authentification pré-authentification dans le chemin du fournisseur de services SAML de Citrix
NetScaler ADC / NetScaler Gateway
(CTX696939,
publié le 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). Signalé par Samarth Vashisht de l'équipe
de tests d'intrusion de JPMorgan Chase.
L'appliance décode en base64 le paramètre RelayState d'une réponse SAML et, lorsque le texte en
clair commence par ctx=, transmet le reste au désérialiseur de contexte nFactor. Sur une version
non corrigée, un échec de désérialisation propage la longueur du RelayState décodé comme code
de disposition interne de la requête au lieu d'une erreur, de sorte qu'un attaquant non authentifié
choisit la branche interne que l'appliance emprunte ensuite uniquement en choisissant la longueur du
RelayState. Certaines branches créent une véritable session Gateway ; d'autres font planter le
moteur de paquets et redémarrent l'appliance. Ce script ne fait ni l'un ni l'autre — il envoie la
seule longueur validée pour ne créer aucune session et ne pas toucher au moteur de paquets, et
répond à une seule question par cible : cette appliance est-elle vulnérable ? Un résultat autre
que VULNERABLE n'est pas en soi un certificat de bonne santé.
# 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+, bibliothèque standard uniquement — aucun paquet tiers.
Pointez l'outil vers le serveur virtuel Gateway ou AAA, pas vers l'interface de gestion.
L'exposition est propre à chaque serveur virtuel, donc une appliance comportant plusieurs VIP doit
être testée pour chacune. La sonde porte une enveloppe de sécurité stricte — une seule longueur de
RelayState validée, jamais balayée — que
Est-il sûr de l'exécuter ? expose.
| Option | Description |
|---|---|
TARGET | Une ou plusieurs cibles [https://]HOST[:PORT] ; le schéma par défaut est https:// |
-f, --targets-file FILE | Lire les cibles depuis un fichier (une par ligne ; commentaires #) |
--timeout SECS | Délai d'attente par requête (par défaut : 15) |
--workers N | Cibles simultanées (par défaut : 16) ; la sortie reste dans l'ordre d'entrée |
-b, --brief | Une seule ligne alignée par cible — idéal pour scanner de nombreux hôtes |
--json | Émettre du JSON structuré, incluant chaque requête envoyée par cible |
--no-color | Désactiver la sortie colorée (respecte aussi NO_COLOR et les non-TTY) |
Une appliance vulnérable (la sortie par défaut sur deux lignes). Le marqueur [!] et
VULNERABLE s'affichent en rouge sur 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
Une appliance corrigée :
$ ./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)
Le garde-fou anti-faux-positif qui se déclenche. La sonde et le contrôle de même longueur ont tous deux renvoyé le signal non corrigé, donc la réponse ne dépend pas de ce qui a été envoyé et la réponse d'apparence décisive est retirée :
$ ./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
Balayage d'un parc (--brief). Les deux lignes gateway.example.com sont les serveurs virtuels
SP et IdP-only sur la même appliance — les deux répondent, ce qu'une vérification de précondition de
configuration ne parviendrait pas à faire :
$ ./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
Sortie lisible par machine (--json). Chaque requête est incluse, de sorte qu'une conclusion
peut être redérivée à partir des preuves plutôt que d'être crue sur parole. Le contrôle est
enregistré par sa relation à la sonde plutôt que comme un verdict qui lui est propre, car un
contrôle qui se lit comme une version corrigée est le résultat attendu sur toutes les versions :
$ ./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"
}
]
}
]
Oui. Il est conçu pour un usage en production et en évaluation :