针对 CVE-2026-8452 的安全、非破坏性补丁状态检查——这是存在于 Citrix NetScaler ADC / NetScaler Gateway SAML 签名规范化器中的预认证堆溢出漏洞(CTX696604,CVSS 8.8)。在规范化过程中,一个过大的独占规范化 PrefixList 会溢出固定大小的缓冲区,而 NetScaler 在验证携带该列表的签名之前就执行这一操作——因此,无需任何凭据、会话或有效签名即可触达整个路径。由 JPMorgan Chase XOR 团队的 Michael Tucker 报告;根因与利用分析归功于 watchTowr Labs。
该脚本不会利用此漏洞,也不会破坏内存。它针对每个目标回答一个问题:此设备上是否存在修复?——该判断基于行为观察,通过观察补丁而非猜测构建版本来确定。
安全。它专为生产环境和评估用途而设计:
PrefixList 并拒绝 513 字节及以上。该限制精确到字节,在两个受支持分支上完全相同,且不随设备配置或周围 SAML 消息的结构而变化——通过探测两条路由(它们以差异显著的 XML 包装该值)并发现它们在同一字节处改变行为,这一点得到了确认。575 字节以 63 字节的余量越过该限制,因此判定结果不依赖于目标的具体设置方式。PrefixList 属性。将其他字段夸大超过该限制——断言消费者服务 URL、颁发者名称、算法标识符、摘要和签名值——在修复构建上不会引起任何变化,因此应用修复不应导致原本可用的 SAML 配置开始失效。如果修改探测,请勿更改
PROBE_PREFIXES,也不要扫描长度区间。 575 字节是关键所在。其他PrefixList长度可能会使设备不稳定——至少在某个携带此修复的构建上出现过一次——因此长度扫描不是探索此漏洞的安全方式,更短也并不意味着更安全。
已修补构建会干净地拒绝过大的 PrefixList,并返回一条独特的消息。未修补构建则会穿过解析器,返回一个通用的内部错误。同一个请求,两种不同的应答:
575 字节 PrefixList | 应答 |
|---|---|
| 未修补 | 500 Internal Server Error 43549 |
| 已修补 | 200 Malformed Assertion sent to Netscaler |
会尝试两条路由,先 IdP,一旦其中一条给出应答即停止。任意一条单独使用都足够,两者合起来覆盖两种 SAML 角色:
| 路由 | 请求 | 要求 |
|---|---|---|
| 1(首选) | POST /saml/login — 签名后的 AuthnRequest,PrefixList 位于 ds:SignedInfo 中 | 绑定到目标 vserver 的 SAML IdP 策略 |
| 2(后备) | POST /cgi/samlauth — SAMLResponse,PrefixList 位于断言签名中 | 目标 vserver 上的 SAML SP 断言消费者服务 |
IdP 路由首先被尝试,因为它是两者中更稳健的一个。它对 Issuer 值、AssertionConsumerServiceURL 以及时钟偏移不敏感——即使 IssueInstant 远远超出设备的偏移容限,仍能正确判别,因为规范化先于时间检查和签名检查执行。
路由 1 的
AuthnRequest必须签名。 未签名的请求在已修补和未修补构建上都会返回200 Malformed Assertion sent to Netscaler,这与已修补信号逐字节相同,因此省略签名块的探测会把每台设备都报告为已修补。签名不需要有效,本工具的签名也确实是无效的;它只需存在,因为正是其SignedInfo将PrefixList带入规范化器。
两个受支持分支都在其修复构建处恰好改变行为,且两条路由均如此:
| 构建 | 判定 | |
|---|---|---|
13.1-63.16 | 最后一个存在漏洞的 13.1 | VULNERABLE |
13.1-63.18 | 第一个修复的 13.1 | PATCHED |
14.1-66.59 | 存在漏洞的 14.1 | VULNERABLE |
14.1-72.61 | 第一个修复的 14.1 | PATCHED |
13.1-63.16 和 63.18 是连续版本,因此该变化可归因于补丁本身,而非中间构建之间的漂移。
这些是这个修复首次出现的构建,而该探测正是检测这一转变。它们已不再是应升级到的构建:后续公告已取代它们,因此 13.1-63.18 和 14.1-72.61 在此都会应答 PATCHED,但仍暴露于较新的问题之下。当前修复构建请参阅 修复措施。
因为在原理上它就无法针对此漏洞奏效。13.1-63.16 和 13.1-63.18 是紧邻修复两侧的构建,它们提供逐字节相同的 tmindex.html、base.css 以及 resources.js——该修复不触碰任何 Web 资源。静态资源哈希在各分支之间也存在碰撞,因此基于哈希的方法可能将易受攻击的设备解析为已修补的构建,并报告其为干净设备——这是检测工具最糟糕的失败模式。因此,构建指纹识别被刻意不实现。补丁状态来自探测,或者在你有凭据时来自 show ns version。
./cve_2026_8452_check.py https://gateway.example.com
./cve_2026_8452_check.py https://gateway.example.com:9443
./cve_2026_8452_check.py -f targets.txt --brief
./cve_2026_8452_check.py -f targets.txt --json > results.json
将工具指向 **Gateway 或 AAA 虚拟服务器**,而不是管理接口。前提条件是针对每个虚拟服务器,因此拥有多个 VIP 的设备需要逐一测试。
### 选项
| Flag | 描述 |
| --- | --- |
| `URL` | 一个或多个 `https://HOST[:PORT]` 目标 |
| `-f, --targets-file FILE` | 从文件读取目标(每行一个;`#` 注释) |
| `-b, --brief` | 每个目标单行对齐输出 — 结论、目标、原因标记 — 用于扫描大量主机 |
| `--json` | 输出结构化 JSON 结果 |
| `--no-color` | 禁用彩色输出(同样遵循 `NO_COLOR` 和非 TTY) |
| `--timeout SECS` | 每次请求的超时时间(默认:15) |
### 示例
**未修补的设备,**在 IdP 路由上响应并已对照控制确认:```console
$ ./cve_2026_8452_check.py https://gateway.example.com:9443
====================================================================
CVE-2026-8452 - NetScaler SAML PrefixList patch-state check
https://gateway.example.com:9443
====================================================================
>> Identifying the appliance
[ OK ] NetScaler indicators: 5 (CSP contains citrixng://)
>> Probing patch state (64 prefixes / 575 bytes, confirmed against a 35-byte control)
idp /saml/login HTTP 500 / 43549: no size check present
idp /saml/login 35-byte control: HTTP 200 "message timestamp outside the appliance's skew tolerance": a different SAML condition, not the size check
[FAIL] Size check absent (via the IDP route)
====================================================================
RESULT: VULNERABLE
====================================================================
https://gateway.example.com:9443 via IDP [size-check-absent]
The size check is absent. This appliance is unpatched for
CVE-2026-8452. Upgrade to 13.1-63.21+ / 14.1-73.32+ (12.1 and
13.0 are EOL and never fixed).
====================================================================
控制行值得仔细阅读:35字节的请求通过了大小检查,
随后因其过期的 IssueInstant 而被拒绝,而575字节的探测请求则根本没走到那一步。
这正是整个方法所依赖的顺序——规范化在时间检查之前运行,
正如它在签名检查之前运行一样。
已修补的设备, 对修复版本发出的相同请求。只有探测行不同——
超大的 PrefixList 按名称被拒绝,而不是落入内部错误:```console
Probing patch state (64 prefixes / 575 bytes, confirmed against a 35-byte control) idp /saml/login HTTP 200 "Malformed Assertion": size check rejected the probe idp /saml/login 35-byte control: HTTP 200 "message timestamp outside the appliance's skew tolerance": a different SAML condition, not the size check [ OK ] Size check present (via the IDP route)
RESULT: PATCHED
https://vpn.example.com via IDP [size-check-present]
**回退到 SP 路由。** 此处 IdP 端点可访问,但没有 IdP 策略绑定到
该虚拟服务器,因此路由 1 弃权,路由 2 应答。当*两个*路由都不匹配策略时
裁决为 `INCONCLUSIVE`,标记为 `no-policy-match` — 绝不会是 `PATCHED`,这正是
该裁决存在的原因:```console
>> Probing patch state (64 prefixes / 575 bytes, confirmed against a 35-byte control)
idp /saml/login HTTP 200 "Matching policy not found": parser not reached
sp /cgi/samlauth HTTP 500 / 43549: no size check present
sp /cgi/samlauth 35-byte control: HTTP 200 "assertion rejected before the size check": a different SAML condition, not the size check
[FAIL] Size check absent (via the SP route)
RESULT: VULNERABLE
对照组捕获了它。 此处端点在这两种长度下都以修补后的消息响应,因此 大小检查从未被执行,看似决定性的结论被撤回。这就是误报 防护机制在起作用,而触发它的原因被明确说明,而非留待推断:```console
Probing patch state (64 prefixes / 575 bytes, confirmed against a 35-byte control) idp /saml/login HTTP 200 "Malformed Assertion": size check rejected the probe idp /saml/login 35-byte control: HTTP 200 "Malformed Assertion": size check rejected the probe [WARN] Probe and control answered alike, so the size check was never exercised
RESULT: INCONCLUSIVE
https://sp-strict.example.com via IDP [flat-response]