
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)
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]
The 575-byte probe and the 35-byte control got the same answer, so this endpoint replies the same way whatever it is sent and the size check was never exercised. Unknown, not patched.
**フリートのスキャン** (`--brief`) では、ターゲットごとに整列された1行が出力され、各行の末尾には理由タグが付きます。終了ステータスは
`1` です(いずれかのターゲットが `VULNERABLE` の場合):```console
$ ./cve_2026_8452_check.py -f targets.txt --brief; echo "exit: $?"
VULNERABLE https://gateway.example.com:9443 size-check-absent
VULNERABLE https://gateway.example.com:9444 size-check-absent
PATCHED https://vpn.example.com size-check-present
INCONCLUSIVE https://sp-strict.example.com flat-response
INCONCLUSIVE https://gw-nopolicy.example.com no-policy-match
UNAFFECTED https://mgmt.example.com no-saml-endpoint
ERROR https://offline.example.com not-identified
exit: 1
機械可読出力(--json)は、試行されたすべてのルートを記録します。
verdict、reason、detail が信頼できるフィールドであり、attempts は生の証拠です。
そのため、個々の試行は、verdict が INCONCLUSIVE であるターゲット上で patched と表示される場合があります:```console
$ ./cve_2026_8452_check.py https://vpn.example.com --json
[
{
"target": "https://vpn.example.com",
"verdict": "PATCHED",
"reason": "size-check-present",
"route": "idp",
"detail": "HTTP 200 "Malformed Assertion": size check rejected the probe",
"attempts": [
{
"route": "idp",
"path": "/saml/login",
"state": "patched",
"detail": "HTTP 200 "Malformed Assertion": size check rejected the probe"
},
{
"route": "idp",
"path": "/saml/login",
"state": "control:known-error",
"detail": "35-byte control: HTTP 200 "message timestamp outside the appliance's skew tolerance": a different SAML condition, not the size check"
}
],
"netscaler_indicators": [
"CSP contains citrixng://",
"CSP contains com.citrix.nsgclient://",
"CSP contains nsgcepa://",
"CSP report-uri /nscsp_violation/report_uri",
"/vpn/js/rdx/ present (HTTP 404)"
]
}
]
## 判定
すべての判定には、その背後にある条件を示す短い `reason` タグが付属しています。`--brief` はこのタグを3列目として出力し、`--json` は `reason` として保持します。
| 判定 | Reason タグ | 意味 |
| --- | --- | --- |
| `VULNERABLE` | `size-check-absent` | サイズチェックが存在しません。このアプライアンスは未パッチです。パッチを適用してください。 |
| `PATCHED` | `size-check-present` | プローブが到達したコードパスでサイズチェックが発動しました。**この CVE に限定:** アプライアンスが最新ビルドであることを意味するものではありません。 |
| `UNAFFECTED` | `no-saml-endpoint` | この仮想サーバーでは SAML エンドポイントが応答しなかったため、脆弱なパスには到達できません。**アプライアンス単位ではなく vserver 単位:** 同じマシン上の別の vserver や VIP に SAML が構成されている可能性があります。 |
| `INCONCLUSIVE` | `flat-response` | エンドポイントが 575 バイトのプローブと 35 バイトの対照に同一の応答を返したため、サイズチェックは実行されませんでした。決定的に見えた回答は撤回されます。これは誤検知ガードが作動したことを示します。 |
| `INCONCLUSIVE` | `no-policy-match` | SAML エンドポイントは応答しましたが、バインドされたポリシーがプローブに一致しなかったため、どちらの経路も canonicalizer に到達しませんでした。 |
| `INCONCLUSIVE` | `other-saml-error` | 認識可能だが診断にはならない SAML 状態によって、サイズチェックの前にプローブが拒否されました。別の長さ制限、署名ポリシー、タイムスタンプなどが原因です。 |
| `INCONCLUSIVE` | `unrecognized-reply` | SAML サーフェスが、認識された集合の外にある何かで応答しました。 |
| `ERROR` | `not-identified` | NetScaler として識別されないか、到達不能です。 |
4 つの `INCONCLUSIVE` の理由はすべて、意思決定において同じことを意味します。**不明、パッチ未適用。** `show ns version` で確認してください。このタグは、再実行する前にオペレーターに *どの* 条件を修正すべきかを伝えるためのものです。プローブを別の仮想サーバーに向けるか、一致するポリシーをバインドします。
`INCONCLUSIVE` は独自の終了コードを持つ独立した判定として存在します。なぜなら、脆弱なアプライアンスがプローブへの応答を拒否する可能性があるからです。仮想サーバーにバインドされた SAML ポリシーがプローブのリクエストに一致しない場合、アプライアンスは canonicalizer より前で短絡し、診断に役立つものを何も返しません。単に一致しないだけのスキャナーはそのようなホスト上で沈黙し、沈黙は「パッチ済み」と読まれます。このツールは代わりに不明として報告します。
### すべての判定は対照と照合して確認される
`PATCHED` と `VULNERABLE` はどちらも *単一の* 識別可能な応答に依存しているため、ツールは応答が実際に送信内容に依存していることを検証します。決定的な回答の後、ツールはリクエストを短い 35 バイトの `PrefixList`(あらゆるサイズチェックより下)で繰り返し、2 つの回答が異なる場合にのみ判定が成立します。一致した場合、エンドポイントは受信内容に関係なく同じ方法で応答するため、サイズチェックは実行されておらず、結果は `INCONCLUSIVE` になります。
これは仮説ではありません。`samlRejectUnsignedAssertion STRICT` で構成されたサービスプロバイダーは、正規化 *前* に署名欠落のためにプローブを拒否し、あらゆる長さでパッチ済みメッセージで応答します。対照がない場合、そのようなアプライアンスは終了コード 0 で `PATCHED` を報告します。これは実際に脆弱なビルドで観察されました。現在は理由タグ `flat-response` 付きで `INCONCLUSIVE` を報告し、実行結果はサイズチェックが実行されなかったことを明確に示します。同じ対照は、エンドポイントが解析しなかったリクエストに対して汎用の内部エラーを返すという鏡像ケースも捉えます。
### 認識される非診断応答
NetScaler SAML エンドポイントには多数の応答候補があり、そのうちの 2 つだけがパッチ状態を確定します。ツールは他の 20 個を認識し、応答ボディをそのまま返す代わりに、ルートごとの行にその条件を表示します。たとえば:```text
idp /saml/login HTTP 200 "post body over the appliance's maximum": a different length limit rejected the probe first
sp /cgi/samlauth HTTP 200 "assertion rejected before the size check": a different SAML condition, not the size check
そのうち3つは長さ制限です — 過大なPOSTボディ、過大な RelayState、過度に長い抽出ユーザー名。これらが最も重要です。なぜなら、パッチ状態を判別するチェックに到達する前に、リクエストが別の長さチェックによって拒否されたことを意味するからです。これは通常、アプライアンスに問題がないことを意味するのではなく、プローブを別の仮想サーバーに向ける必要があることを意味します。
20のうちの1つは、すべてのIdPルート実行で出現します: コントロールのmessage timestamp outside the appliance's skew tolerance。プローブは固定の IssueInstant を保持しているため、サイズチェックを通過したコントロールは、その古さゆえに拒否されます — これはアプライアンスの特性ではなく、プローブの特性です。20のセット外の応答は、名前付き条件の代わりに、応答自体が最初の行として出力されます。
これらはすべて依然として INCONCLUSIVE を生成し、other-saml-error とタグ付けされます。応答を認識しても、それが PATCHED に昇格することは決してありません。それを実現するのは明示的なパッチ適用済みビルドの応答だけです。また、認識されない応答もすべて、unrecognized-reply として INCONCLUSIVE に分類されます。この認識機能は、ターゲットを分類できなかった理由をオペレーターに伝えるために存在するのであり、分類するために存在するのではありません。
| コード | 意味 |
|---|---|
0 | パッチ適用済み、または対象の仮想サーバーで影響なし |
1 | 少なくとも1つのターゲットが VULNERABLE |
2 | 使用法エラー(不正な引数 / 読み取り不能なターゲットファイル) |
3 | 少なくとも1つのターゲットが INCONCLUSIVE、脆弱なものはなし |
4 | 少なくとも1つのターゲットがエラー、脆弱または判定不能のものはなし |
2 は不正な呼び出しに対するargparse自身の終了コードであるため、判定コードはこれをスキップします。したがって、ラッパースクリプトは「このアプライアンスは分類できなかった」(3)と「ツールの呼び出し方を誤った」(2)を区別できます。2 を多重定義する方式ではこれができません。
複数ターゲットのスイープ全体では、終了コードは最悪のステータスではなく優先度によって選択されます: VULNERABLE > INCONCLUSIVE > ERROR > クリーン。したがって、到達不能なホストが終了コード内の脆弱な発見を覆い隠すことは決してありません。
PATCHED と終了コード 0 は、プローブが到達したパスにCVE-2026-8452のサイズチェックが存在することを意味します。これらは、このバグの後に開示され、後のビルドで修正されたものを含め、他のNetScalerの脆弱性については何も述べていません。このツールの終了コード 0 を、アプライアンスの健全性の証明として受け取らないでください。UNAFFECTED は「ここでは到達不能」を意味し、「このアプライアンスは安全」ではありません。/saml/login はアプライアンス全体に適用されますが、IdPポリシーがバインドされていない仮想サーバーでは Matching policy not found と応答し、正規化処理の前にショートサーキットします。プローブのリクエストが満たさない式ルールをポリシーが使用するIdPは、回答を返す代わりに INCONCLUSIVE になります。INCONCLUSIVE は健全性の証明ではありません。 これは、沈黙が合格結果と誤認されることがないよう、別の終了コードで意図的に PATCHED と区別されています。INCONCLUSIVE を報告します。 SAMLポリシーが仮想サーバーに直接バインドされるのではなく、nextFactor によって到達されるポリシーラベル内にある場合、未承諾のアサーションは一致するポリシーを見つけられず、正規化処理の前にショートサーキットします。脆弱なビルドで検証され、INCONCLUSIVE が報告されました。デバイスポスチャまたはログインスキーマの最初の要素の背後にあるSAMLは一般的なパターンであるため、nFactorゲートウェイでの INCONCLUSIVE は「おそらく到達可能。show ns version で確認」として扱い、単なる興味本位として扱わないでください。VULNERABLE の判定は、欠落しているサイズチェック、つまりパッチ状態を確認します。攻撃者があなたのビルドで破損をどこまで悪用できるかは測定しません。13.1-63.21 以降、または 14.1-73.32 以降にアップグレードしてください(FIPSおよびNDcPP: 14.1-73.32 FIPS、または13.1-FIPSおよび13.1-NDcPPの場合は 13.1-37.277)。
CVE-2026-8452自体の修正は、CTX696604 によると 13.1-63.18 / 14.1-72.61 で初めて提供され、このツールが検出するのはまさにその移行です。これらのビルドはその後、CTX696939(2026-08-19)によって置き換えられました。この勧告は CVE-2026-19489 と CVE-2026-19490 を追加しており、後者はCVSS 9.3の事前認証認証バイパスです。14.1-43.56 / 13.1-61.28 以降のビルドでのその前提条件は、構成済みのSAMLアクションです — したがって、このツールがチェックするバグの対象範囲内にあるアプライアンスは、その脆弱性の対象範囲内でもある可能性が高く、ここでの PATCHED の判定はアップグレードを延期する理由にはなりません。両方の勧告は上記のビルドで解決されます。
12.1または13.0のアプライアンスには修正がなく、今後も提供されません — これらのブランチはサポート終了であり、恒久的に脆弱なものとして扱い、サポート対象のブランチに移行する必要があります。
さらに2つの注意事項:
HAペアの両方のノードにパッチを適用してください。 パッチ未適用のセカンダリは、引き継いだ瞬間に完全に露出したアプライアンスになります。
仮想サーバーのタイプではなく、SAML設定によってインベントリをスコープしてください。 ベンダー勧告の表現(GatewayまたはAAA仮想サーバー)は、トリガー条件よりも広範です。実行中の設定で add authentication samlAction と add authentication samlIdPProfile を、add authentication vserver と add vpn vserver と合わせて確認してください。
これは推測ではなく、テスト済みです。脆弱であることが確認されたアプライアンス上で、すべてのSAMLオブジェクトを削除し、その場所に非SAML認証要素をバインドし、AAA仮想サーバーを起動して稼働させたままにしました: するとSAMLエンドポイントはすべてのリクエストに対して 404 を返しました。SAML設定がない場合、それらは単にポリシーでゲートされているだけではありません — 存在しないのです。したがって、SAMLが設定されていない仮想サーバーは、このバグの対象範囲外であり、そのようなターゲットでの UNAFFECTED は盲点ではなく実際の回答です。依然として適用される注意点は上記のスコープに関するものです: これは仮想サーバーごとのものであるため、アプライアンスについて結論を出すのではなく、各VIPを確認してください。
CVE-2026-8452は、同じ勧告で5つの関連する脆弱性とともに公開されました。その次に追跡する価値があるのは CVE-2026-8451 です。これはSAML IdPパスにおける事前認証メモリ過読み取りで、実環境での活発な悪用が確認されています。この2つは攻撃対象領域を共有しているため、同じ設定監査で両方をカバーできます。
このコードはMITライセンスの下で配布されています。
事前の相互同意なしにターゲットを攻撃するためにこのツールを使用することは違法です。該当するすべての地域、州、連邦の法律を遵守する責任はエンドユーザーにあります。開発者は一切の責任を負わず、このプログラムによって引き起こされた誤用または損害について責任を負いません。