
Citrix NetScaler CVE-2026-8452 向けの行動ベースのパッチ状態検出器。メモリを悪用または破損させることなく、細工した SAML リクエストを送信して PrefixList サイズチェックが存在するかどうかを判定します。
安全で非破壊的なパッチ状態チェックで、CVE-2026-8452 — Citrix NetScaler ADC / NetScaler Gateway SAML 署名カノナライザーにおける事前認証ヒープオーバーフロー
(CTX696604、
CVSS 8.8) を対象としています。過大な排他カノナライゼーションの PrefixList は、カノナライゼーション中に固定サイズのバッファをオーバーフローさせます。NetScaler は、その PrefixList を運ぶ署名を検証する前にカノナライゼーションを実行するため、この経路全体は、認証情報なし、セッションなし、有効な署名なしで到達可能です。JPMorgan Chase XOR チームの Michael Tucker 氏によって報告されました。根本原因および悪用の分析は、watchTowr Labs によるものです。
このスクリプトはバグを悪用せず、メモリを破損させません。ターゲットごとに1つの質問に答えます: このアプライアンスに修正が存在するか? — ビルドを推測するのではなく、パッチを観察することで、動作に基づいて判定されます。
はい。本番環境および評価での使用を目的として設計されています:
PrefixList について 512 バイトを受け入れ、513 バイト以上を拒否します。この制限はバイト単位で特定され、両方のサポート対象ブランチで同一であり、アプライアンスの構成や周囲の SAML メッセージの形状によって変動しません。これは、値を大幅に異なる量の XML で包む両方のルートをプローブし、それらが同じバイトで動作を変えることを確認することで裏付けられています。575 は制限を 63 バイト上回るため、判定はターゲットがたまたまどのように設定されているかに依存しません。PrefixList 属性に適用されます。他のフィールドをそれ以上に膨らませても — アサーションコンシューマサービス URL、発行者名、アルゴリズム識別子、ダイジェストおよび署名値 — 修正済みビルドでは何も変わりません。したがって、修正を適用しても、動作中の SAML 構成が失敗し始めることはないはずです。プローブを変更する場合は、
PROBE_PREFIXESを変更せず、長さをスイープしないでください。 575 バイトは重要です。他のPrefixListの長さはアプライアンスを不安定にする可能性があり、少なくとも1つのケースではこの修正を含むビルドで発生しました。したがって、長さのスイープはこのバグを調査する安全な方法ではなく、短くしても安全になるわけではありません。
パッチ適用済みビルドは、過大な PrefixList を、特徴的なメッセージとともにきれいに拒否します。未適用ビルドはパーサーをすり抜け、一般的な内部エラーを返します。同一のリクエストに対して、2つの異なる応答があります:
575バイトの PrefixList | 応答 |
|---|---|
| 未適用 | 500 Internal Server Error 43549 |
| 適用済み | 200 Malformed Assertion sent to Netscaler |
2つのルートが試行され、最初に IdP が試行され、いずれかが応答を返すとすぐに停止します。どちらか一方だけで十分であり、一緒に使用すると両方の SAML ロールをカバーします:
| ルート | リクエスト | 必要条件 |
|---|---|---|
| 1 (最初) | POST /saml/login — 署名付き AuthnRequest、ds:SignedInfo 内の PrefixList | 対象の vserver にバインドされた SAML IdP ポリシー |
| 2 (フォールバック) | POST /cgi/samlauth — SAMLResponse、アサーション署名内の PrefixList | 対象の vserver 上の SAML SP アサーションコンシューマサービス |
IdP ルートが最初に試行されるのは、2つのうちでより堅牢だからです。これは、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 と応答しますが、新しい問題には引き続き露出しています。現在の修正ビルドについては、Remediation を参照してください。
なぜなら、このバグに対しては、原理的にも機能しないからです。修正の直前と直後のビルドである 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を持つアプライアンスではそれぞれをテストする必要があります。
### Options
| Flag | Description |
| --- | --- |
| `URL` | 1つ以上の`https://HOST[:PORT]`ターゲット |
| `-f, --targets-file FILE` | ファイルからターゲットを読み取ります(1行に1つ、`#`はコメント) |
| `-b, --brief` | ターゲットごとに1行の整列した出力 — 判定、ターゲット、理由タグ — 多数のホストをスキャンする場合用 |
| `--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が応答します。*どちらの*ルートもポリシーに一致しない場合、
判定は `no-policy-match` タグが付けられた `INCONCLUSIVE` となります — 決して `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)