CVE-2026-88771, CVSS 4.0 pari a 9.5, è finita nel catalogo KEV di CISA il 27 settembre 2026 perché già sfruttata in the wild come zero-day, prima ancora che Citrix rilasciasse una patch.
Non si tratta di una race condition sofisticata, né di una corruzione di memoria che richiede settimane di reverse engineering per essere sfruttata in modo affidabile. La CVE è finita nel catalogo KEV di CISA il 27 settembre 2026 proprio perché già sfruttata in the wild come zero-day, prima ancora che Citrix rilasciasse una patch. A renderla ancora più insidiosa è il fatto che colpisce la configurazione di default di NetScaler ADC e NetScaler Gateway, senza richiedere feature particolari abilitate.
E ora che watchTowr Labs ne ha pubblicato l’analisi tecnica completa insieme a un proof-of-concept funzionante su GitHub, vale la pena guardare da vicino quanto sia elementare la logica che sta dietro a un bug di questa portata.
La falla fa parte del bollettino Citrix CTX697096, che in un colpo solo ha sanato otto CVE (da 88771 a 88778), due dei quali (questo e CVE-2026-88772 un buffer overflow raggiungibile via DTLS sui virtual server VPN) risultano già sfruttati attivamente. Le versioni corrette sono NetScaler ADC e Gateway 14.1-73.37, 13.1-64.23, oltre alle rispettive build FIPS e NDcPP.
Dove si nasconde il problema: non è (per una volta) nsppe
Chi si occupa da anni di reversing su Citrix NetScaler ha imparato ad aspettarsi che ogni RCE nasca dentro il binario nsppe (es. CitrixBleed), il cuore del motore di elaborazione pacchetti che ha generato la maggior parte delle vulnerabilità critiche dell’ultimo decennio su questa piattaforma.
Questa volta no. Confrontando la build vulnerabile (14.1-73.30) con quella corretta (14.1-73.37), i ricercatori di watchTowr hanno individuato la modifica chiave in un file che difficilmente ci si aspetterebbe protagonista di una RCE pre-auth: ns_monuploadd_err.pl, uno script Perl di manutenzione che si occupa di recuperare i core file generati quando un processo Pitboss (il supervisore dei Packet Engine di NetScaler) muore inaspettatamente.
Il meccanismo, nella sua versione vulnerabile, è la classica ricetta per il disastro. Prendere dati che arrivano dall’esterno, farli finire in un file di log, e poi rielaborarli con una pipeline shell costruita per interpolazione di stringhe.
Il codice originale cercava nei log una riga del tipo pitboss: NSPPE-00 (12345) unexpectedly died, e con una sequenza di grep, tail, sed e awk, tutta racchiusa tra backtick Perl, che dicono all’interprete “esegui questo come comando di shell e restituiscimi l’output”, ne estraeva il nome del Packet Engine e il PID per ricostruire il nome del relativo core file, qualcosa come NSPPE-00-12345.
Il problema è che lo script si fida ciecamente di qualsiasi cosa segua la stringa NSPPE nel log, senza validare che si tratti davvero di un identificativo numerico legittimo. E qui arriva il punto centrale: quella riga di log non nasce da un evento di sistema fidato, ma può essere scritta a partire da un input controllato dall’attaccante.
L’iniezione: un campo login, due punti e virgola
Il vettore d’ingresso più diretto è l’endpoint di autenticazione /nf/auth/doAuthentication.do, lo stesso preso di mira dal PoC pubblicato su GitHub.
Basta inviare una richiesta POST non autenticata con un campo login costruito ad arte (riga 31 del PoC valorizzato):
login_payload = f"pitboss PPE unexpectedly died NSPPE/var/tmp/watchTowr;# X" ; #Pat & Mat
Il tentativo di login fallisce, ovviamente non è quello l’obiettivo.
Ma NetScaler, come previsto, logga il valore di quel campo in diversi file (i log SSLVPN, quelli del processo nsaaad che gestisce l’autenticazione), portando con sé, testuale, la stringa iniettata. È qui che si consuma il vero difetto architetturale della vulnerabilità: chiunque riesca a far finire un proprio input in un log di sistema, un tentativo di login fallito, uno User-Agent, persino un parametro di richiesta bloccato da un rate limit, ha di fatto un canale verso l’esecuzione di comandi, perché quel log verrà prima o poi processato dallo script Perl e reinterpolato in una seconda chiamata a backtick:
my $WR_PPE_CORE = `find $OFF_DIR/var/core -name ${WR_PPE_COREFILE_NAME}* -print | tail -1`;
A quel punto la variabile $WR_PPE_COREFILE_NAME contiene già il punto e virgola iniettato, e la shell lo interpreta esattamente come farebbe con un comando digitato da terminale. Esegue find, incontra il separatore ;, ed esegue di seguito il comando arbitrario dell’attaccante (il resto verrà commentato dal #).
Nell’esempio del PoC, un semplice id > /var/tmp/watchTowr che scrive l’output del comando su un file, prova inconfutabile dell’esecuzione. E poiché su NetScaler la quasi totalità dei processi di sistema, script di manutenzione compresi, gira come root, il comando iniettato eredita gli stessi privilegi.
Non è un dettaglio da poco: significa che l’exploit non richiede alcuna credenziale valida, nessuna sessione, nessuna interazione con componenti complessi come il motore SSL o il parser HTTP. È letteralmente un attacker-controlled string che percorre log→file→backtick→shell, senza un solo controllo di validazione lungo il tragitto.
Il PoC di watchTowr: minimalismo quasi disarmante
Lo script pubblicato da Sina Kheirkhah su GitHub (watchTowr-vs-Citrix-Netscaler-CVE-2026-88771.py) riduce l’intera catena a poche righe di Python. Accetta come parametri il target e il comando da eseguire, costruisce il payload pitboss PPE unexpectedly died NSPPE;{comando};# X, lo url-encoda e lo invia via POST al form di autenticazione, insieme ai campi standard passwd, savecredentials e al pulsante di login. Nessuna libreria di exploiting, nessuna gestione di sessioni, nessun bypass di protezioni aggiuntive: una singola richiesta HTTP costruita a mano basterebbe altrettanto bene, tant’è che lo stesso watchTowr fornisce l’equivalente in formato richiesta raw per chi preferisce Burp Suite.
L’unico vero ostacolo pratico, semmai, è temporale: ns_monuploadd_err.pl non gira in tempo reale a ogni richiesta, ma viene eseguito periodicamente, con un intervallo che può arrivare fino a 24 ore prima che il payload iniettato venga effettivamente processato ed eseguito. Un dettaglio che, se da un lato rallenta lo sfruttamento “a comando”, dall’altro lo rende perfetto per un attacco silenzioso e difficile da correlare temporalmente con la richiesta HTTP che lo ha originato, esattamente il tipo di caratteristica che rende una vulnerabilità appetibile per un attore APT più che per un opportunista.
La correzione: niente di rivoluzionario, solo l’igiene che mancava
La patch Citrix mostra bene quanto la falla fosse evitabile a monte.
La pipeline shell grep | sed | awk è stata sostituita con una lettura nativa dei file di log in Perl, applicando un’espressione regolare rigida che cattura solo un nome di Packet Engine nel formato NSPPE-\d{2} e un PID puramente numerico, qualunque altro carattere presente sulla riga di log, comprese le sequenze di command injection, viene semplicemente ignorato.
La seconda modifica, altrettanto significativa, sostituisce la chiamata find tramite backtick con la forma “a lista” di Perl (open my $find, '-|', 'find', ...), che passa ogni argomento separatamente al processo senza mai invocare una shell intermedia: i metacaratteri come ;, ` o | perdono così ogni significato speciale. Infine il path restituito viene validato con un’ulteriore regex ancorata che ammette solo caratteri alfanumerici, slash, trattini e punti.
In sostanza, la correzione applica esattamente i due principi che, se rispettati dall’inizio, avrebbero reso impossibile la vulnerabilità: whitelisting rigoroso dell’input prima di usarlo in un contesto sensibile, ed esecuzione di processi esterni senza passare da una shell quando non è strettamente necessario.
Cosa fare ora
Con un exploit pubblico disponibile, una CVSS 9.5 e conferma di sfruttamento attivo da parte di più fonti (Citrix, CISA KEV, Unit 42), il livello di urgenza per chi gestisce appliance NetScaler esposte è massimo.
Va detto che, a differenza di molti prodotti cloud-native, NetScaler non dispone di un meccanismo di aggiornamento automatico: applicare la patch richiede di scaricare manualmente il firmware dal portale Citrix, caricarlo sull’appliance, avviare l’installazione e pianificare un riavvio, un processo che, su cluster in alta affidabilità, va ripetuto nodo per nodo.
Sono passaggi che richiedono tempo e una finestra di manutenzione, un lusso che un attaccante con un PoC pubblico in mano non è tenuto a concedere.
Oltre all’aggiornamento alle build corrette (14.1-73.37, 13.1-64.23 o le equivalenti FIPS/NDcPP), lo stesso bollettino CTX697096 raccomanda di verificare, post-patch, la presenza di segni di compromissione pregressa (file sconosciuti, account creati ex novo, sessioni anomale).
Vista la finestra temporale di esecuzione ritardata del payload, un’infrastruttura compromessa prima della patch potrebbe mostrare tracce di esecuzione comandi anche a distanza di ore dal tentativo di sfruttamento originale, rendendo la sola applicazione dell’aggiornamento insufficiente senza un controllo forense a valle.
Link originale dell’analisi: https://labs.watchtowr.com/oh-look-the-foot-gun-went-off-again-citrix-netscaler-preauth-command-injection-CVE-2026-88771/
Link del poc pubblico: https://github.com/watchtowrlabs/watchTowr-vs-Citrix-Netscaler-CVE-2026-88771
Bollettino Citrix: https://support.citrix.com/support-home/kbsearch/article?articleNumber=CTX697096&articleTitle=Citrix_NetScaler_ADC_and_Citrix_NetScaler_Gateway_Security_Bulletin_for_CVE_2026_88771_CVE_2026_88772_CVE_2026_88773_CVE_2026_88774_CVE_2026_88775_CVE_2026_88776_CVE_2026_88777_and_CVE_2026_88778
Ho iniziato la mia carriera occuparmi nella ricerca e nell’implementazioni di soluzioni in campo ICT e nello sviluppo di applicazioni. Al fine di aggiungere aspetti di sicurezza in questi campi, da alcuni anni ho aggiunto competenze inerenti al ramo offensive security (OSCP), occupandomi anche di analisi di sicurezza e pentest in molte organizzazioni.
Aree di competenza: Ethical Hacking, Bug Hunting, Penetration Testing, Red Teaming, Security Research, Cybersecurity Communication