
Rilevatore comportamentale dello stato di patch per Citrix NetScaler CVE-2026-8452. Invia richieste SAML appositamente predisposte per determinare se il controllo della dimensione di PrefixList è presente, senza sfruttare o corrompere la memoria.
Un controllo sicuro e non distruttivo dello stato di patch per CVE-2026-8452, l'heap overflow pre-autenticazione nel canonicalizzatore della firma SAML di Citrix NetScaler ADC / NetScaler Gateway (CTX696604, CVSS 8.8). Un PrefixList di canonicalizzazione esclusiva di dimensioni eccessive supera un buffer a dimensione fissa durante la canonicalizzazione, che NetScaler esegue prima di validare la firma che lo trasporta — quindi l'intero percorso è raggiungibile senza credenziali, senza sessione e senza firma valida. Segnalato da Michael Tucker del team XOR di JPMorgan Chase; l'analisi della causa principale e dello sfruttamento è attribuita a watchTowr Labs.
Questo script non sfrutta la vulnerabilità e non corrompe la memoria. Risponde a una sola domanda per ogni target: la correzione è presente su questo appliance? — determinata in base al comportamento, osservando la patch piuttosto che indovinando la build.
Sì. È progettato per l'uso in produzione e durante le valutazioni:
PrefixListPrefixList. Gonfiare altri campi oltre tale limite — URL del servizio di consumo assertion, nomi dell'emittente, identificatori di algoritmo, valori di digest e firma — non cambia nulla su una build corretta, quindi applicare la correzione non dovrebbe far fallire una configurazione SAML funzionante.Se modifichi la sonda, non cambiare
PROBE_PREFIXESe non eseguire scansioni su più lunghezze. 575 byte sono un elemento portante. Altre lunghezze diPrefixListpossono destabilizzare un appliance, in almeno un caso su una build che include questa correzione, quindi una scansione di lunghezze non è un modo sicuro per esplorare questa vulnerabilità e più corto non significa più sicuro.
Le build patchate rifiutano un PrefixList di dimensioni eccessive in modo pulito, con un messaggio distintivo. Le build non patchate superano il parser e restituiscono un errore interno generico. Una richiesta identica, due risposte diverse:
PrefixList da 575 byte | Risposta |
|---|---|
| Non patchata | 500 Internal Server Error 43549 |
| Patchata | 200 Malformed Assertion sent to Netscaler |
Vengono provate due route, prima la IdP, fermandosi appena una fornisce una risposta. Una sola è sufficiente, e insieme coprono entrambi i ruoli SAML:
| Route | Richiesta | Richiede |
|---|---|---|
| 1 (prima) | POST /saml/login — AuthnRequest firmata, PrefixList in ds:SignedInfo | una policy SAML IdP associata al vserver di destinazione |
| 2 (fallback) | POST /cgi/samlauth — SAMLResponse, PrefixList nella firma dell'assertion | un servizio di consumo assertion SAML SP sul vserver di destinazione |
La route IdP viene per prima perché è la più robusta delle due. È insensibile al valore di Issuer, a AssertionConsumerServiceURL e alla deriva dell'orologio — un IssueInstant ben al di fuori della tolleranza di deriva dell'appliance discrimina comunque correttamente, perché la canonicalizzazione precede sia il controllo temporale sia il controllo della firma.
La
AuthnRequestdella route-1 deve essere firmata. Una non firmata restituisce200 Malformed Assertion sent to Netscalersia sulle build patchate che su quelle non patchate, un segnale byte-identico a quello delle build patchate, quindi una sonda che omette il blocco della firma segnala ogni appliance come patchato. La firma non deve essere valida, e quella di questo strumento non lo è; deve solo essere presente, perché è il suoSignedInfoa trasportare ilPrefixListnel canonicalizzatore.
Entrambi i rami supportati cambiano comportamento esattamente in corrispondenza della build di correzione, su entrambe le route:
| Build | Verdetto | |
|---|---|---|
13.1-63.16 | ultima 13.1 vulnerabile | VULNERABLE |
13.1-63.18 | prima 13.1 corretta | PATCHED |
14.1-66.59 | 14.1 vulnerabile | VULNERABLE |
14.1-72.61 | prima 14.1 corretta | PATCHED |
13.1-63.16 e 63.18 sono release consecutive, quindi il cambiamento è attribuibile alla patch stessa piuttosto che a derive tra build intermedie.
Queste sono le build in cui è apparsa per la prima volta questa correzione, e la sonda rileva esattamente quella transizione. Non sono più le build a cui aggiornare: bollettini successivi le hanno superate, quindi sia 13.1-63.18 che 14.1-72.61 rispondono PATCHED qui pur rimanendo esposte a problemi più recenti. Vedi Remediation per le build corrette attuali.
Perché non può funzionare su questa vulnerabilità, nemmeno in linea di principio. 13.1-63.16 e 13.1-63.18, le build immediatamente a cavallo della correzione, servono tmindex.html, base.css e resources.js byte-identici — la correzione non tocca alcuna risorsa web. Anche gli hash delle risorse statiche collidono tra i rami, quindi un approccio basato su hash può risolvere un appliance vulnerabile in una build patchata e segnalarlo come pulito, che è la modalità di errore peggiore per uno strumento di rilevamento. Il fingerprinting della build è quindi deliberatamente non implementato. Lo stato della patch deriva dalla sonda, o da show ns version dove si dispone delle credenziali.
./cve_2026_8452_check.py https://gateway.example.com
./cve_2026_8452_check.py https://gateway.example.com:9443
./cve_2026_8452_check.py -f targets.txt --brief
./cve_2026_8452_check.py -f targets.txt --json > results.json
Punta lo strumento al **Gateway o server virtuale AAA**, non all'interfaccia di gestione. La
precondizione è per virtual server, quindi un'appliance con più VIP richiede il test di ciascuno.
### Opzioni
| Flag | Descrizione |
| --- | --- |
| `URL` | Uno o più target `https://HOST[:PORT]` |
| `-f, --targets-file FILE` | Leggi i target da un file (uno per riga; commenti `#`) |
| `-b, --brief` | Singola riga allineata per target — verdetto, target, tag del motivo — per scansionare molti host |
| `--json` | Emetti risultati JSON strutturati |
| `--no-color` | Disabilita l'output colorato (rispetta anche `NO_COLOR` e non-TTY) |
| `--timeout SECS` | Timeout per richiesta (predefinito: 15) |
### Esempi
**Un'appliance non patchata,** risponde sul percorso IdP e viene confermata rispetto al controllo:```console
$ ./cve_2026_8452_check.py https://gateway.example.com:9443
====================================================================
CVE-2026-8452 - NetScaler SAML PrefixList patch-state check
https://gateway.example.com:9443
====================================================================
>> Identifying the appliance
[ OK ] NetScaler indicators: 5 (CSP contains citrixng://)
>> Probing patch state (64 prefixes / 575 bytes, confirmed against a 35-byte control)
idp /saml/login HTTP 500 / 43549: no size check present
idp /saml/login 35-byte control: HTTP 200 "message timestamp outside the appliance's skew tolerance": a different SAML condition, not the size check
[FAIL] Size check absent (via the IDP route)
====================================================================
RESULT: VULNERABLE
====================================================================
https://gateway.example.com:9443 via IDP [size-check-absent]
The size check is absent. This appliance is unpatched for
CVE-2026-8452. Upgrade to 13.1-63.21+ / 14.1-73.32+ (12.1 and
13.0 are EOL and never fixed).
====================================================================
La riga di controllo merita una lettura attenta: la richiesta da 35 byte supera il controllo della dimensione e viene poi respinta per il suo IssueInstant scaduto, mentre la sonda da 575 byte non è mai arrivata a quel punto. Questo è l'ordine su cui si basa l'intero metodo: la canonicalizzazione avviene prima del controllo temporale, proprio come avviene prima del controllo della firma.
Un'appliance patchata, la stessa richiesta contro la build corretta. Solo la riga della sonda differisce: la PrefixList sovradimensionata viene respinta per nome invece di ricadere nell'errore interno:```console
Probing patch state (64 prefixes / 575 bytes, confirmed against a 35-byte control) idp /saml/login HTTP 200 "Malformed Assertion": size check rejected the probe idp /saml/login 35-byte control: HTTP 200 "message timestamp outside the appliance's skew tolerance": a different SAML condition, not the size check [ OK ] Size check present (via the IDP route)
RESULT: PATCHED
https://vpn.example.com via IDP [size-check-present]
**Ripiegamento sulla route SP.** Qui l'endpoint IdP è raggiungibile ma nessuna policy IdP è associata
a questo virtual server, quindi la route 1 si astiene e la route 2 risponde. Quando *nessuna delle due*
route corrisponde a una policy, il verdetto è `INCONCLUSIVE` contrassegnato con `no-policy-match` — mai
`PATCHED`, che è il motivo stesso per cui quel verdetto esiste:```console
>> Probing patch state (64 prefixes / 575 bytes, confirmed against a 35-byte control)
idp /saml/login HTTP 200 "Matching policy not found": parser not reached
sp /cgi/samlauth HTTP 500 / 43549: no size check present
sp /cgi/samlauth 35-byte control: HTTP 200 "assertion rejected before the size check": a different SAML condition, not the size check
[FAIL] Size check absent (via the SP route)
RESULT: VULNERABLE
Il controllo lo ha rilevato. Qui l'endpoint ha risposto con il messaggio corretto a entrambe le lunghezze, quindi il controllo della dimensione non è mai stato esercitato e la risposta apparentemente decisiva viene ritirata. Questo è l'intervento della protezione contro i falsi positivi, e il motivo per cui è scattata è dichiarato anziché lasciato dedurre:```console
Probing patch state (64 prefixes / 575 bytes, confirmed against a 35-byte control) idp /saml/login HTTP 200 "Malformed Assertion": size check rejected the probe idp /saml/login 35-byte control: HTTP 200 "Malformed Assertion": size check rejected the probe [WARN] Probe and control answered alike, so the size check was never exercised
RESULT: INCONCLUSIVE
https://sp-strict.example.com via IDP [flat-response]
The 575-byte probe and the 35-byte control got the same answer, so this endpoint replies the same way whatever it is sent and the size check was never exercised. Unknown, not patched.
**Scansione di una flotta** (`--brief`), una riga allineata per target che termina con il tag del motivo. Lo stato di uscita è `1` se un qualsiasi target è `VULNERABLE`:```console
$ ./cve_2026_8452_check.py -f targets.txt --brief; echo "exit: $?"
VULNERABLE https://gateway.example.com:9443 size-check-absent
VULNERABLE https://gateway.example.com:9444 size-check-absent
PATCHED https://vpn.example.com size-check-present
INCONCLUSIVE https://sp-strict.example.com flat-response
INCONCLUSIVE https://gw-nopolicy.example.com no-policy-match
UNAFFECTED https://mgmt.example.com no-saml-endpoint
ERROR https://offline.example.com not-identified
exit: 1
Output leggibile dalla macchina (--json), che registra ogni route tentata. verdict, reason e detail sono i campi autorevoli; attempts è l'evidenza grezza, quindi un singolo tentativo può indicare patched su un target il cui verdetto è INCONCLUSIVE:```console
$ ./cve_2026_8452_check.py https://vpn.example.com --json
[
{
"target": "https://vpn.example.com",
"verdict": "PATCHED",
"reason": "size-check-present",
"route": "idp",
"detail": "HTTP 200 "Malformed Assertion": size check rejected the probe",
"attempts": [
{
"route": "idp",
"path": "/saml/login",
"state": "patched",
"detail": "HTTP 200 "Malformed Assertion": size check rejected the probe"
},
{
"route": "idp",
"path": "/saml/login",
"state": "control:known-error",
"detail": "35-byte control: HTTP 200 "message timestamp outside the appliance's skew tolerance": a different SAML condition, not the size check"
}
],
"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)"
]
}
]
## Verdicts
Ogni verdetto include un breve tag `reason` che identifica la condizione che lo determina. `--brief` stampa il tag come terza colonna, mentre `--json` lo riporta come `reason`.
| Verdict | Tag motivo | Significato |
| --- | --- | --- |
| `VULNERABLE` | `size-check-absent` | Il controllo di dimensione è assente. Questo appliance non è patchato — applica la patch. |
| `PATCHED` | `size-check-present` | Il controllo di dimensione è scattato sul percorso di codice raggiunto dalla sonda. **Limitato a questa CVE:** non significa che l'appliance sia su una build corrente. |
| `UNAFFECTED` | `no-saml-endpoint` | Nessun endpoint SAML ha risposto su questo virtual server, quindi il percorso vulnerabile non è raggiungibile qui. **Per vserver, non per appliance:** SAML potrebbe essere configurato su un altro vserver o VIP sulla stessa macchina. |
| `INCONCLUSIVE` | `flat-response` | L'endpoint ha risposto in modo identico alla sonda da 575 byte e al controllo da 35 byte, quindi il controllo di dimensione non è mai stato esercitato. La risposta dall'aspetto decisivo viene ritirata — è il guardiano dei falsi positivi che scatta. |
| `INCONCLUSIVE` | `no-policy-match` | Un endpoint SAML ha risposto, ma nessuna policy associata corrispondeva alla sonda, quindi nessuno dei due percorsi ha raggiunto il canonicalizzatore. |
| `INCONCLUSIVE` | `other-saml-error` | Una condizione SAML riconosciuta ma non diagnostica ha respinto la sonda prima del controllo di dimensione — un diverso limite di lunghezza, una policy di firma, un timestamp. |
| `INCONCLUSIVE` | `unrecognized-reply` | Una superficie SAML ha risposto con qualcosa al di fuori dell'insieme riconosciuto. |
| `ERROR` | `not-identified` | Non identificato come NetScaler, o non raggiungibile. |
Tutti e quattro i motivi `INCONCLUSIVE` significano la stessa cosa ai fini della decisione — **sconosciuto, non patchato.** Conferma con `show ns version`. Il tag serve a indicare all'operatore *quale* condizione correggere prima di rilanciare: puntare la sonda su un virtual server diverso, oppure associare una policy corrispondente.
`INCONCLUSIVE` esiste come verdetto distinto, con il proprio codice di uscita, perché un appliance vulnerabile può rifiutarsi di rispondere alla sonda. Se la policy SAML associata a un virtual server non corrisponde alla richiesta della sonda, l'appliance va in cortocircuito prima del canonicalizzatore e non restituisce nulla di diagnostico. Uno scanner che semplicemente non trova corrispondenza diventa silenzioso su un host del genere, e il silenzio viene letto come "patchato". Questo strumento lo segnala invece come sconosciuto.
### Ogni verdetto è confermato tramite un controllo
`PATCHED` e `VULNERABLE` si basano entrambi su una *singola* risposta distintiva, quindi lo strumento verifica che la risposta dipenda effettivamente da ciò che è stato inviato. Dopo una risposta decisiva, ripete la richiesta con un breve `PrefixList` da 35 byte — al di sotto di qualsiasi controllo di dimensione — e il verdetto regge solo se le due risposte differiscono. Se coincidono, l'endpoint risponde allo stesso modo qualunque cosa riceva, il controllo di dimensione non è mai stato esercitato e il risultato è `INCONCLUSIVE`.
Non è un'ipotesi. Un service provider configurato con `samlRejectUnsignedAssertion STRICT` respinge la sonda per una firma mancante *prima* della canonicalizzazione e risponde con il messaggio da patchato a qualsiasi lunghezza. Senza il controllo, un appliance del genere riportava `PATCHED` con exit 0 — osservato su una build realmente vulnerabile. Ora riporta `INCONCLUSIVE` con il tag motivo `flat-response` e l'esecuzione dichiara a chiare lettere che il controllo di dimensione non è mai stato esercitato. Lo stesso controllo intercetta anche il caso speculare, in cui un endpoint restituisce l'errore interno generico a richieste che non ha mai analizzato.
### Risposte riconosciute non diagnostiche
Un endpoint SAML di NetScaler ha un ampio insieme di possibili risposte e solo due di esse stabiliscono lo stato di patch. Lo strumento ne riconosce 20 delle altre e indica la condizione sulla riga relativa al percorso, invece di restituire il corpo della risposta, ad esempio:```text
idp /saml/login HTTP 200 "post body over the appliance's maximum": a different length limit rejected the probe first
sp /cgi/samlauth HTTP 200 "assertion rejected before the size check": a different SAML condition, not the size check
Tre di esse sono limiti di lunghezza — un corpo POST troppo grande, un RelayState troppo grande, un nome utente estratto troppo lungo. Queste contano di più, perché significano che la richiesta è stata rifiutata da un controllo di lunghezza diverso prima di raggiungere quello che distingue lo stato della patch. Di solito significa che la sonda deve essere puntata su un server virtuale diverso, non che l'appliance sia a posto.
Uno dei 20 compare in ogni esecuzione del percorso IdP: il message timestamp outside the appliance's skew tolerance del controllo. La sonda trasporta un IssueInstant fisso, quindi un controllo che supera il controllo di dimensione viene poi rifiutato per la sua età — una proprietà della sonda, non dell'appliance. Una risposta al di fuori dell'insieme dei 20 viene stampata come prima riga autonoma invece di una condizione denominata.
Tutte producono comunque INCONCLUSIVE, etichettate come other-saml-error. Riconoscere una risposta non la promuove mai a PATCHED: solo una risposta esplicita di build patchata lo fa, e ogni risposta non riconosciuta ricade anch'essa in INCONCLUSIVE — come unrecognized-reply. Il riconoscimento serve a dire a un operatore perché un target non ha potuto essere classificato, non a classificarlo.
| Code | Meaning |
|---|---|
0 | Patchato, o non interessato sul server virtuale preso di mira |
1 | Almeno un target è VULNERABLE |
2 | Errore di utilizzo (argomenti non validi / file dei target illeggibile) |
3 | Almeno un target è INCONCLUSIVE, nessuno vulnerabile |
4 | Almeno un target ha generato un errore, nessuno vulnerabile o non conclusivo |
2 è il codice di uscita di argparse per un'invocazione errata, quindi i codici di verdetto lo saltano. Uno script wrapper può quindi distinguere "questa appliance non ha potuto essere classificata" (3) da "ho chiamato male lo strumento" (2), cosa che uno schema che sovraccarica 2 non potrebbe.
In un'analisi multi-target il codice viene scelto per priorità, non per lo stato peggiore: VULNERABLE > INCONCLUSIVE > ERROR > pulito. Un host irraggiungibile quindi non maschera mai un riscontro di vulnerabilità nel codice di uscita.
PATCHED e codice di uscita 0 significano che il controllo della dimensione di CVE-2026-8452 è presente sul percorso raggiunto dalla sonda. Non dicono nulla su qualsiasi altra vulnerabilità NetScaler, incluse quelle divulgate dopo questo bug e corrette in build successive. Non interpretare l'uscita 0 di questo strumento come un attestato di piena salute per un'appliance.UNAFFECTED significa "non raggiungibile qui", non "questa appliance è sicura"./saml/login riguarda l'intera appliance, ma su un server virtuale senza policy IdP associata risponde Matching policy not found e va in cortocircuito prima del canonicalizzatore. Un IdP la cui policy usa una regola di espressione che la richiesta della sonda non soddisfa finirà in INCONCLUSIVE piuttosto che dare una risposta.INCONCLUSIVE non è un attestato di piena salute. È deliberatamente distinto da PATCHED con un codice di uscita separato, così che il silenzio non venga mai scambiato per un risultato positivo.INCONCLUSIVE. Quando la policy SAML si trova in un'etichetta di policy raggiunta da nextFactor invece di essere associata direttamente al server virtuale, un'asserzione non richiesta non trova alcuna policy corrispondente e va in cortocircuito prima del canonicalizzatore. Verificato su una build vulnerabile, che ha riportato INCONCLUSIVE. Poiché SAML dietro un primo fattore device-posture o login-schema è uno schema comune, tratta INCONCLUSIVE su un gateway nFactor come "probabilmente raggiungibile, conferma con show ns version", non come una curiosità.VULNERABLE conferma la mancanza del controllo di dimensione, che è lo stato della patch. Non misura quanto lontano un attaccante potrebbe spingere la corruzione sulla tua build.Aggiorna a 13.1-63.21 o successiva, oppure 14.1-73.32 o successiva (FIPS e NDcPP: 14.1-73.32 FIPS, oppure 13.1-37.277 per 13.1-FIPS e 13.1-NDcPP).
Il fix per la stessa CVE-2026-8452 è stato distribuito per la prima volta in 13.1-63.18 / 14.1-72.61 secondo CTX696604, e questa è la transizione che questo strumento rileva. Quelle build sono state successivamente superate da CTX696939 (2026-08-19), che aggiunge CVE-2026-19489 e CVE-2026-19490, quest'ultima un bypass di autenticazione pre-autenticazione con CVSS 9.3. La sua precondizione sulle build dalla 14.1-43.56 / 13.1-61.28 in poi è un'azione SAML configurata — quindi un'appliance nell'ambito del bug che questo strumento controlla è probabilmente nell'ambito anche di quello, e un verdetto PATCHED qui non è un motivo per rimandare l'aggiornamento. Entrambi i bollettini sono risolti dalle build sopra indicate.
Le appliance su 12.1 o 13.0 non hanno fix e non lo riceveranno — quei rami sono a fine vita e le appliance devono essere trattate come permanentemente vulnerabili e migrate a un ramo supportato.
Due ulteriori note:
Applica la patch a entrambi i nodi di una coppia HA. Un secondario non patchato è un'appliance completamente esposta nel momento in cui subentra.
Delimita il tuo inventario in base alla configurazione SAML, non al tipo di server virtuale. La formulazione dell'avviso del vendor (Gateway o AAA virtual server) è più ampia della condizione di attivazione. Controlla la configurazione in esecuzione per add authentication samlAction e add authentication samlIdPProfile insieme a add authentication vserver e add vpn vserver.
Questo è testato, non dedotto. Su un'appliance confermata vulnerabile abbiamo rimosso ogni oggetto SAML,
associato al suo posto un fattore di autenticazione non SAML, e lasciato i server virtuali AAA attivi e funzionanti:
gli endpoint SAML hanno quindi restituito 404 a ogni richiesta. Non sono semplicemente limitati da policy senza
configurazione SAML — non esistono. Quindi un server virtuale senza SAML è realmente fuori ambito per
questo bug, e UNAFFECTED su un tale target è una risposta reale, non un punto cieco. L'avvertenza che
vale ancora è quella dell'ambito di cui sopra: è per server virtuale, quindi conferma ogni VIP piuttosto che
trarre conclusioni sull'appliance.
CVE-2026-8452 è stata distribuita insieme a cinque vulnerabilità sorelle nello stesso bollettino. Quella che vale la pena monitorare accanto ad essa è CVE-2026-8451, un overread di memoria pre-autenticazione nel percorso SAML IdP che ha visto uno sfruttamento attivo in natura. Le due condividono una superficie di attacco, quindi la stessa verifica della configurazione copre entrambe.
Questo codice è distribuito sotto una licenza MIT.
L'utilizzo di questo strumento per attaccare target senza il preventivo consenso reciproco è illegale. È responsabilità dell'utente finale rispettare tutte le leggi locali, statali e federali applicabili. Gli sviluppatori non si assumono alcuna responsabilità e non sono responsabili di qualsiasi uso improprio o danno causato da questo programma.