CVE-2026-8452: diseccionando el Pre-Auth RCE de NetScaler que convirtió un heap overflow en un write-what-where
Hay vulnerabilidades que empiezan con un simple memcpy() demasiado confiado y terminan con alguien teniendo control de RIP en uno de los appliances que hacen de puerta de entrada a una red corporativa. CVE-2026-8452 es un buen ejemplo.
Oficialmente, Citrix describe el problema como una vulnerabilidad de tipo memory overflow que puede provocar comportamiento impredecible, errores o denegación de servicio. La condición de explotación publicada por el fabricante requiere que NetScaler ADC o NetScaler Gateway esté configurado como Gateway —SSL VPN, ICA Proxy, CVPN o RDP Proxy— o como AAA virtual server. Citrix le asigna CWE-119 y una puntuación CVSS v4 de 8.8.
Pero la parte realmente interesante aparece cuando dejamos de mirar el CVE como una entrada de una base de datos de vulnerabilidades y empezamos a analizarlo como lo haría un exploit developer. Porque la pregunta deja de ser: ¿Dónde está el buffer overflow? y pasa a ser: ¿Qué primitive puedo construir a partir de ese overflow?
La investigación publicada por watchTowr es especialmente interesante precisamente por eso: no se queda en demostrar que el proceso se cae, sino que reconstruye cómo una corrupción de memoria puede transformarse en una primitiva de escritura arbitraria, después en control del flujo de ejecución y finalmente en ejecución de código.
Y ahí es donde CVE-2026-8452 se convierte en un magnífico caso práctico de exploit development sobre un appliance de red.
Antes de empezar: una pequeña precisión sobre CVE-2026-8452
Hay una cierta peculiaridad en la atribución de esta vulnerabilidad. El advisory de Citrix agrupa varias vulnerabilidades de NetScaler y acredita en conjunto a Michael Tucker, del XOR team de JPMorgan Chase, Aliz Hammond de watchTowr y Maxim Suhanov. Citrix no asigna públicamente cada CVE a cada investigador.
Por eso, cuando watchTowr reconstruye la vulnerabilidad mediante patch diffing, la identificación concreta de CVE-2026-8452 debe entenderse en el contexto de esa investigación.
Es un detalle menor, pero importante cuando hablamos de vulnerability research: una cosa es lo que dice explícitamente el fabricante y otra la reconstrucción técnica que hacemos a partir del parche y del comportamiento del software.
1. El camino hasta el heap overflow
La historia comienza en un lugar bastante poco sospechoso: SAML.
SAML utiliza XML y, como cualquier protocolo basado en XML que incorpore firmas digitales, tiene que resolver un problema fundamental: dos documentos XML que representan la misma estructura lógica pueden tener representaciones textuales diferentes.
Por ejemplo, estas dos estructuras pueden ser equivalentes desde el punto de vista XML aunque su representación textual no sea idéntica:
<data id="123" type="example">
Hello
</data>y:
<data type="example" id="123">Hello</data>Para poder firmar y verificar XML de forma determinista existe la canonicalización.
El problema aparece cuando el resultado de esa canonicalización acaba siendo considerablemente más grande que el buffer que el código espera recibir. Como veis la secuencia conceptual es sencilla:
El bug no está en que SAML tenga firmas. Tampoco en que XML tenga canonicalización. El problema está en asumir que el resultado de esa transformación cabe siempre en un espacio de memoria determinado. Y esa es una de las reglas más antiguas de la explotación de memoria: si el tamaño de los datos está controlado por el atacante, el tamaño del destino tiene que estar controlado por el programa.
2. ¿Qué demonios hace realmente la canonicalización XML?
Para entender el bug hay que detenerse un momento aquí. Una firma XML no se calcula directamente sobre el XML tal y como aparece en el documento. Antes hay que obtener una representación canónica.
Primero tenemos el XML original. Después se aplica el algoritmo de canonicalización, normalmente conocido como C14N. El resultado es una representación determinista del documento o de la parte del documento que se está firmando.
A continuación se calcula un digest criptográfico de esa representación y se utiliza durante la verificación de la firma. El proceso, simplificando mucho, sería conceptualmente:
Esto tiene una consecuencia importante para nuestra vulnerabilidad: la canonicalización puede transformar una entrada relativamente compacta en una representación mucho mayor.
Y si una parte de esa representación está influenciada por el atacante, tenemos exactamente el tipo de situación que interesa a un investigador de corrupción de memoria.
3. El parche es probablemente la pista más importante
Cuando el código fuente no está disponible, una de las herramientas más potentes que tenemos es el patch diffing.
En lugar de intentar comprender un binario gigantesco desde cero, comparamos:
versión vulnerable
+
versión parcheada
↓
funciones modificadasLa idea es sencilla.
Si un fabricante corrige una vulnerabilidad añadiendo una comprobación de tamaño antes de un memcpy(), probablemente acabamos de encontrar la huella del bug.
watchTowr utilizó precisamente este enfoque sobre nsppe, el packet-processing engine de NetScaler.
La comparación redujo el universo de investigación a un conjunto mucho más pequeño de funciones modificadas y permitió localizar cambios relacionados con el procesamiento SAML.
Uno de los cambios relevantes consistía precisamente en introducir comprobaciones de tamaño antes de copiar datos asociados a SignedInfo. Conceptualmente, la diferencia es:
No hay que interpretar ese código como el código fuente literal de NetScaler, sino como una representación conceptual del cambio de seguridad. Y aquí aparece una de las técnicas que más me gustan del reverse engineering de vulnerabilidades: buscar qué condición nueva ha tenido que introducir el fabricante para que el bug desaparezca.
Muchas veces el parche explica el bug mejor que el propio advisory.
4. PrefixList: el pequeño detalle que permite controlar el overflow
Dentro de SignedInfo aparece otro elemento interesante relacionado con la canonicalización: PrefixList.
Este atributo permite especificar una lista de prefijos y, desde el punto de vista de un atacante, tiene una característica especialmente interesante: puede contener una cantidad considerable de información controlada por el usuario.
Esto resulta perfecto para estudiar el comportamiento del heap.
En lugar de enviar simplemente una cadena enorme formada por:
AAAA AAAA AAAA AAAA AAAA...podemos utilizar valores identificables:
N0000000
N0000001
N0000002
N0000003
...Esto parece una tontería, pero en reversing es extremadamente útil.
Si posteriormente encontramos en memoria:
N0000124sabemos inmediatamente qué parte de nuestro input ha llegado hasta ese punto.
Es decir, estamos convirtiendo el payload en una especie de cinta métrica. En vez de preguntar: ¿Qué bytes estoy controlando? podemos preguntar: ¿Qué offset de mi input está controlando este campo?
Y esa información es fundamental para reconstruir la geometría del heap.
5. Del overflow a la corrupción de otro objeto
Aquí empieza la parte realmente interesante.
El resultado de la canonicalización termina almacenándose en estructuras utilizadas por NetScaler para manejar buffers de red.
El overflow no tiene por qué destruir inmediatamente el proceso. Puede continuar escribiendo más allá del límite del buffer y empezar a sobrescribir información perteneciente al siguiente objeto que se encuentre en memoria.
Conceptualmente podemos imaginar algo así. La diferencia entre un crash y una explotación fiable empieza precisamente aquí. No nos interesa únicamente poder escribir fuera del buffer. Nos interesa saber qué estructura estamos sobrescribiendo. Y, todavía más importante: qué campos de esa estructura son utilizados posteriormente por el programa.
Si conseguimos sobrescribir un campo que posteriormente se interpreta como un puntero, acabamos de transformar una corrupción genérica en una primitive mucho más interesante.
6. El salto importante: write-what-where
Y aquí aparece una de las expresiones mágicas del exploit development: write-what-where.
La idea es muy sencilla:
WHAT = datos que queremos escribir
WHERE = dirección donde queremos escribirSi conseguimos controlar ambos elementos, tenemos una primitiva de escritura arbitraria.
En este caso, el objeto corrupto contiene información utilizada posteriormente durante operaciones de copia.
Conceptualmente:
memcpy(
destination->data,
source->data,
length
);Si podemos manipular:
source->data
destination->dataentonces podemos transformar una operación aparentemente inocente de copia de datos en una escritura controlada por el atacante.
El paso conceptual es:
heap overflow
↓
corrupt object
↓
corrupt pointer
↓
memcpy()
↓
write-what-whereY éste es probablemente el momento más importante de toda la explotación. Porque ya no estamos ante: "Tengo un heap overflow." Ahora tenemos: "Tengo una primitive que potencialmente me permite escribir en una dirección elegida."
Eso cambia completamente las posibilidades.
7. Encontrando algo útil que sobrescribir
Una primitive de escritura arbitraria no sirve de mucho si no tenemos un objetivo interesante.
Así que el siguiente paso consiste en buscar dentro del proceso estructuras que contengan punteros de función o datos que puedan convertirse en control de flujo.
En la investigación de watchTowr aparece un candidato especialmente interesante: tx_pkt_complete_fptr. El nombre ya da una pista bastante buena.
Estamos ante un function pointer utilizado durante el procesamiento de paquetes.
En el camino de ejecución aparece una secuencia conceptualmente similar a:
mov rax, [tx_pkt_complete_fptr]
pop rbp
jmp raxPara un exploit developer esto es casi perfecto.
Si conseguimos convertir:
tx_pkt_complete_fptren:
0x4141414141414141y posteriormente el proceso intenta hacer:
jmp raxel flujo de ejecución pasa a estar bajo nuestro control.
La cadena se convierte en:
Y acabamos de superar una de las barreras fundamentales de una explotación de memoria: hemos pasado de controlar datos a controlar ejecución.
8. Controlar RIP no significa automáticamente tener una shell
Aquí hay otro detalle que muchas veces se pierde cuando se habla de vulnerabilidades como ésta.
Controlar RIP no significa necesariamente que tengamos inmediatamente una shell funcional.
Todavía tenemos que resolver cuestiones como:
- ¿Dónde colocamos nuestro código?
- ¿Es ejecutable esa memoria?
- ¿Tenemos NX/DEP?
- ¿Podemos utilizar ROP?
- ¿Qué registros controlamos?
- ¿Qué estado tiene el proceso?
- ¿Qué ocurre después de ejecutar nuestro código?
- ¿El proceso va a seguir funcionando?
En la investigación de NetScaler, el entorno de ejecución presenta condiciones especialmente favorables para continuar la explotación.
El objetivo final pasa a ser ejecutar código dentro del proceso comprometido. Pero aquí aparece un nuevo enemigo.
nsppe.
El problema de matar al proceso que acabas de comprometer
Imaginemos que hemos conseguido ejecutar código. Perfecto. Ahora hacemos algo aparentemente trivial: crear archivo o ejecutar una acción y el proceso termina cayéndose.
En un servidor normal quizá tengamos tiempo para establecer una conexión posterior. En un appliance como NetScaler la situación es diferente.
Existe un mecanismo supervisor que detecta determinados fallos del proceso y puede iniciar su recuperación.
Aquí entra en juego pitboss.
La secuencia conceptual es:
Y eso plantea un problema de explotación bastante interesante:
¿Cómo conseguimos que nuestro proceso comprometido sobreviva el tiempo suficiente para hacer algo útil?
Esto nos lleva a un concepto que me parece especialmente interesante para entender la diferencia entre un PoC y un exploit real:
process continuity.
No basta con: RCE → ejecutar código. Un exploit fiable muchas veces necesita:
RCE
↓
hacer lo que necesitamos
↓
reparar el estado corrupto
↓
mantener vivo el proceso
↓
continuar la ejecución normalEsto es especialmente importante en software de infraestructura.
Si acabamos de comprometer el proceso principal de un VPN gateway, un crash puede ser precisamente lo contrario de lo que queremos.
Podemos conseguir ejecución de código y, al mismo tiempo, provocar una interrupción de servicio que haga saltar todas las alarmas.
La explotación perfecta es aquella que consigue el objetivo sin destruir el componente que acabamos de comprometer.
9. Señales y el mecanismo de recuperación
En este punto aparece otra parte interesante de la ingeniería del exploit.
nsppe utiliza handlers para determinadas señales relacionadas con errores graves del proceso. Entre ellas aparecen señales como:
SIGSEGV
SIGBUS
SIGABRTCuando se produce una de estas condiciones, el proceso puede ejecutar su camino de error y comunicar el problema al mecanismo de supervisión.
La idea investigada por watchTowr consiste en modificar el comportamiento de determinados handlers para evitar que el proceso siga inmediatamente la ruta normal de recuperación.
Conceptualmente:
Esto no es simplemente una técnica para conseguir RCE. Es una técnica para intentar resolver el problema que viene después del RCE: mantener el proceso bajo control. Y esa distinción es fundamental.
10. La cadena completa
Si juntamos todos los pasos, la explotación deja de parecer una única vulnerabilidad y empieza a parecer una cadena de pequeñas primitives. El recorrido conceptual es:
Y ésta es precisamente la parte que me parece más interesante del análisis.
Ninguno de los pasos, considerado aisladamente, parece especialmente espectacular.
- Tenemos un parser.
- Tenemos un buffer.
- Tenemos un overflow.
- Tenemos un objeto adyacente.
- Tenemos un puntero.
- Tenemos un
memcpy(). - Tenemos un function pointer.
- Tenemos un
jmp. - Y tenemos un mecanismo supervisor.
El trabajo del exploit developer consiste en conectar todas esas piezas.
El verdadero valor del patch diffing
Hay una lección bastante más general detrás de este caso.
Cuando un fabricante publica: "Memory overflow vulnerability" nos está diciendo muy poco.
Pero cuando conseguimos:
versión vulnerable
↓
versión parcheada
↓
binary diff
↓
funciones modificadas
↓
nuevos checks
↓
root causela situación cambia radicalmente.
El parche se convierte en una especie de mapa inverso de la vulnerabilidad. En particular, las comprobaciones nuevas alrededor de operaciones como:
memcpy()
memmove()
strcpy()
serialization
deserializationmerecen una atención especial.
No significa que cada if (length > ...) esconda un CVE.
Pero cuando aparece una comprobación de tamaño nueva exactamente en el camino que procesa datos controlados por el usuario, tenemos una pista excelente.
Otra lección: el crash es solamente el principio
Una de las cosas que más me gusta de esta investigación es que muestra una progresión muy clara:
Crash
↓
Memory corruption
↓
Controlled corruption
↓
Primitive
↓
Arbitrary write
↓
Control flow
↓
Code executionMuchos análisis de vulnerabilidades terminan en: "hemos conseguido provocar un crash." Desde el punto de vista de exploit development eso puede ser solamente el primer 10% del trabajo. La pregunta importante es qué propiedades tiene esa corrupción. ¿Controlamos el offset? ¿Controlamos los datos? ¿Controlamos el destino? ¿Podemos repetir la corrupción? ¿Es determinista el layout? ¿Hay ASLR? ¿Hay PIE? ¿Hay NX? ¿Hay stack canaries? ¿Podemos encontrar un function pointer? ¿Existe una estructura interesante adyacente?
Ésas son las preguntas que convierten un crash en una investigación de explotación.
¿Por qué los appliances perimetrales son tan interesantes?
Aquí está probablemente la razón por la que este tipo de vulnerabilidad merece tanta atención.
Un servidor web comprometido puede darte acceso a una aplicación. Un appliance perimetral comprometido puede darte acceso a una pieza de infraestructura situada exactamente en el punto donde convergen:
Internet
↓
VPN
↓
Autenticación
↓
Aplicaciones internas
↓
Red corporativaNetScaler no es simplemente otro servidor. Es infraestructura. Y por eso un RCE pre-auth en un appliance de este tipo tiene un perfil de riesgo muy diferente al de una vulnerabilidad equivalente en un sistema aislado.
Citrix, de hecho, recomienda instalar las versiones corregidas con carácter urgente y proporciona además mecanismos para comprobar si el appliance cumple las precondiciones de CVE-2026-8452.
¿Cómo comprobar si el appliance cumple la condición de explotación?
Éste es otro detalle importante.No todos los NetScaler están necesariamente expuestos a esta vulnerabilidad en las mismas condiciones. Citrix especifica para CVE-2026-8452 que el appliance debe estar configurado como:
- AAA virtual server.
- Gateway / VPN vServer.
- ICA Proxy.
- CVPN.
- RDP Proxy.
El propio fabricante proporciona patrones de configuración para identificar estas condiciones. Para una auditoría defensiva, por tanto, no deberíamos quedarnos únicamente con: ¿Tengo una versión vulnerable? sino comprobar: ¿Tengo una versión vulnerable? + ¿Tengo la configuración afectada?
Esto es especialmente importante en grandes organizaciones donde puede haber decenas o cientos de appliances NetScaler.
Versiones afectadas y parche
Según el boletín de Citrix, las ramas afectadas incluyen:
14.1 < 14.1-72.61
13.1 < 13.1-63.18
14.1 FIPS < 14.1-72.61 FIPS
13.1 FIPS / NDcPP < 13.1-37.272Las versiones corregidas indicadas por el fabricante son:
14.1-72.61 o posterior
13.1-63.18 o posterior
14.1-72.61 FIPS o posterior
13.1-37.272 o posterior para FIPS/NDcPPCitrix también indica que las instalaciones de Secure Private Access Hybrid que utilicen NetScaler deben actualizarse.
En otras palabras: si tienes NetScaler expuesto y cumples las condiciones de explotación, no es el típico parche que dejaría para la siguiente ventana de mantenimiento.
El exploit
El exploit de watchTowr encadena un heap overflow pre-auth en el procesamiento SAML con una serie de primitivas de explotación. Primero obtiene del propio flujo SAML el AuthnRequest y el AssertionConsumerService, y construye una SAMLResponse especialmente manipulada cuyo SignedInfo contiene un InclusiveNamespaces/PrefixList diseñado al milímetro. Ese PrefixList no solo provoca el overflow durante la canonicalización XML, sino que contiene valores de 64 bits colocados en offsets concretos para corromper estructuras/punteros del heap y conseguir una primitiva de escritura que termina sobrescribiendo un puntero de función.
A partir de ahí, el exploit redirige el flujo mediante un jmp rax hacia un shellcode fragmentado dentro del propio PrefixList: lay_sc() divide el shellcode en instrucciones y las distribuye por los huecos disponibles, conectándolos mediante JMP cortos. El shellcode modifica el tratamiento de varias señales para evitar que la recuperación del proceso interrumpa la explotación y después crea /var/vpn/theme/x.php, escribe un pequeño webshell PHP y lo deja disponible para ejecutar comandos mediante HTTP. Finalmente, el script espera a que NetScaler vuelva a responder y comprueba el resultado accediendo al webshell.
Referencias











Comentarios
Publicar un comentario