SAML と SSO の内部(アサーションと署名検証)
なぜ偽アサーションが通るのか、署名検証の落とし穴がわかる。SP-Initiated/IdP-Initiated フローと XML 署名の検証手順、Signature Wrapping 攻撃の成立条件を原理から押さえられます。
- 1.SAML SSO は IdP が発行した署名付きアサーションを SP が検証する仕組み。SP-Initiated は SP の AuthnRequest が起点、IdP-Initiated は IdP のポータルが起点で、後者は未要求の応答を受け入れるため CSRF 耐性が低い。
- 2.署名検証は「正しい鍵で・正しい範囲に・正しいアルゴリズムで」署名されているかの3点が要。XML Signature Wrapping は『署名された要素』と『SP が実際に読む要素』をずらして攻撃する。
- 3.SSO セッションは IdP セッションと SP セッションの2層。シングルログアウト(SLO)は全 SP へ伝播が必要で、片方が切れない設計だとセッション残存のリスクになる。
SAML が解く問題:ドメインをまたいでログインを連携する
組織が SaaS を何十も使う時代、サービスごとにパスワードを持たせるのは管理も安全性も破綻します。SAML(Security Assertion Markup Language)2.0 は、認証を一箇所(IdP)に集約し、各サービス(SP)はその結果だけを受け取ることでこれを解きます。役割は2つに分かれます。
- IdP(Identity Provider):本人確認を行い、「この人は誰で、いつ・どう認証したか」を記したアサーションを発行する。
- SP(Service Provider):保護対象のサービス。自分では認証せず、IdP のアサーションを検証してログインを許可する。
鍵になるのは、SP と IdP の間でユーザーの資格情報(パスワード)が一切流れないことです。流れるのは IdP が署名した XML 文書だけで、SP はその署名を検証して「IdP が確かに本人確認した」と信じます。信頼の根拠は共有秘密ではなく、IdP の公開鍵による署名にあります。この点は認証と認可の違いを押さえると役割分担が明確になります。
SAML アサーションは、Subject(誰か)、Conditions(いつまで・どの相手向けか)、AuthnStatement(どう認証したか)を含む XML 要素です。これに IdP がデジタル署名を付すことで、SP は中身の改竄を検出でき、発行元が IdP 本人であることを確認できます。SP がアサーションを信じる根拠は、署名検証に使う IdP の公開鍵(証明書)を事前に交換してある(メタデータ交換)からです。
2つの起点:SP-Initiated と IdP-Initiated
ログインがどこから始まるかで、フローは2種類あります。
| 観点 | SP-Initiated | IdP-Initiated |
|---|---|---|
| 起点 | SP の保護リソースへアクセス | IdP のポータル(アプリ一覧) |
| AuthnRequest | SP が発行し IdP へリダイレクト | なし(IdP がいきなり Response を作る) |
| InResponseTo | 応答に要求 ID が入り突き合わせ可能 | 対応する要求がなく突き合わせ不可 |
| CSRF/リプレイ耐性 | RelayState と ID 照合で防ぎやすい | 未要求の応答を受け入れるため弱い |
| 典型例 | アプリのログインボタン経由 | SSO ポータルからアプリを起動 |
SP-Initiated では、SP が AuthnRequest(一意な ID 付き)を作り、ユーザーのブラウザ経由で IdP へ送ります。IdP は本人確認後に Response を返し、その中の InResponseTo 属性に元の要求 ID を入れます。SP は「自分が出した要求 ID と一致するか」を照合できるため、見覚えのない応答を弾けます。
# SP-Initiated(HTTP-Redirect で要求、HTTP-POST で応答)
1) User -> SP : 保護リソースへアクセス(未ログイン)
2) SP -> User : 302 リダイレクト
Location: IdP/SSO?SAMLRequest=...&RelayState=元URL
3) User -> IdP : AuthnRequest を提示(ID=_req123)
4) IdP : ユーザーを認証(パスワード/MFA など)
5) IdP -> User : HTML フォーム自動 POST
<input name=SAMLResponse value=base64(署名付きアサーション)>
InResponseTo=_req123 を含む
6) User -> SP : SAMLResponse を ACS エンドポイントへ POST
7) SP : 署名検証 → InResponseTo 照合 → セッション確立
IdP-Initiated では AuthnRequest が存在しません。IdP のポータルでアプリを選ぶと、IdP がいきなり署名付き Response を生成して SP へ POST します。対応する要求 ID がないため InResponseTo も入らず、SP は「自分が頼んだ応答か」を確認できません。攻撃者が正規の(しかし他人宛の)応答を被害者のブラウザから SP へ流し込む、ログイン CSRF 的な悪用の余地が生まれます。
InResponseTo で要求と応答を結べないため、IdP-Initiated は構造的にリプレイと CSRF に弱くなります。緩和には (1) アサーションの一意 ID をキャッシュして再利用を拒否、(2) NotOnOrAfter を短く設定して有効期限を厳格化、(3) 可能なら SP-Initiated に統一、が定石です。利便性のために IdP-Initiated を残す場合でも、ID キャッシュによるリプレイ検出は必須です。
XML 署名検証の3つの要点
SAML の安全性は、ほぼすべてアサーションの署名検証が正しいかにかかっています。XML Signature(XML-DSig)の検証は、次の3点が全て満たされて初めて意味を持ちます。
- 正しい鍵か:署名検証に使う鍵が、事前にメタデータで交換した IdP の証明書であること。応答に同梱された
<KeyInfo>の証明書を無批判に信用してはならない。攻撃者が自分の鍵で署名し、その公開鍵を<KeyInfo>に同梱すれば「自己完結して検証が通る」偽署名が作れてしまう。 - 正しい範囲か:署名が実際に SP が読む要素(アサーション本体)を覆っているか。署名対象の
ReferenceのURIが指す ID と、SP が認証情報として読み出す要素が同一であること。ここがずれると後述の Wrapping 攻撃が成立する。 - 正しいアルゴリズムか:ダイジェスト・署名アルゴリズムが安全なものに限定されていること(SHA-1 や、署名を実質無効化する不正な変換を拒否する)。
# XML-DSig の構造(アサーションに付く署名の骨子)
<Signature>
<SignedInfo>
<CanonicalizationMethod .../> # 正規化(空白差などを吸収)
<SignatureMethod Algorithm="...rsa-sha256"/>
<Reference URI="#_assertion_abc"> # ★ この ID の要素を署名対象とする
<DigestMethod Algorithm="...sha256"/>
<DigestValue>...</DigestValue> # 対象要素のハッシュ
</Reference>
</SignedInfo>
<SignatureValue>...</SignatureValue> # SignedInfo への RSA 署名
<KeyInfo>...</KeyInfo> # ★ ここの鍵を鵜呑みにしない
</Signature>
ポイントは、署名されるのは <SignedInfo> であって、<SignedInfo> の中の DigestValue が対象要素のハッシュを保持している、という二段構えです。だから検証は「SignedInfo の署名が正しい」かつ「Reference URI が指す要素のハッシュが DigestValue と一致する」の両方を確認する必要があります。証明書の検証自体はX.509 チェーン検証の知識がそのまま効きます。
XML Signature Wrapping:署名する要素と読む要素をずらす
XML Signature Wrapping(XSW)は、署名検証は正規の要素に対して通るのに、SP が認証情報として読むのは攻撃者が差し込んだ別要素、という「ずれ」を突く攻撃です。成立の核心は、署名検証ロジックと業務ロジックが別々の要素を見てしまう実装にあります。
# 攻撃前:正規の応答(ID=_orig の正しいアサーションに署名)
<Response>
<Assertion ID="_orig"> # 署名対象、Subject=victim
<Subject>victim</Subject>
<Signature><Reference URI="#_orig"/>...</Signature>
</Assertion>
</Response>
# 攻撃後:署名は触らず、偽アサーションを「包んで」挿入
<Response>
<Assertion ID="_evil"> # 偽。Subject=admin(無署名)
<Subject>admin</Subject>
</Assertion>
<Assertion ID="_orig"> # 元のまま。署名は今も正当
<Subject>victim</Subject>
<Signature><Reference URI="#_orig"/>...</Signature>
</Assertion>
</Response>
# 署名検証器:URI=#_orig を検証 → 正当 ✓
# 業務ロジック:getElementsByTagName("Assertion")[0] を読む → _evil(admin)✗
署名検証器は URI="#_orig" を律儀に検証して「正当」と判断します。一方、SP の業務コードが「最初に現れた Assertion を読む」「親をたどらず単純な XPath で拾う」といった素朴な実装だと、署名されていない _evil を本物として処理してしまいます。署名は破られていない(数学的には完全に正当)のに、なりすましが成立するのが厄介な点です。これはデジタル署名アルゴリズム自体の強度とは無関係の、XML 処理層の問題だということを強調しておきます。
XSW の根本原因は「検証する要素」と「使う要素」の分離です。対策は次の通りです。(1) 検証済みの DOM ノードそのものから属性を読む(署名された要素を特定し、その同一ノードから Subject 等を取得する。文書を再パースして別途検索し直さない)。(2) Reference URI が指す ID を持つ要素が文書中にちょうど1つであることを確認し、ID の重複を拒否する。(3) 署名がアサーションを実際に覆っているか(祖先関係)を確認し、兄弟位置に署名がない要素を信用しない。(4) スキーマ検証で想定外の要素挿入を弾く。要するに「署名された当のノードを、検証後に手放さない」のが要です。
SSO セッションのライフサイクル:IdP と SP の2層
SAML SSO のセッションは2層で管理されます。混同するとログアウトの設計を誤ります。
- IdP セッション:ユーザーが IdP で1回認証すると確立。これが生きている限り、新しい SP へ移っても再認証なしでアサーションが発行される(これが「シングル」サインオンの正体)。
- SP セッション:各 SP がアサーション検証後にローカルに張る独自のセッション(Cookie など)。一度張られると、以後は IdP に問い合わせず SP 単独で有効。
# セッションの寿命は独立している
t0: IdP で認証 → IdP セッション開始
t1: SP-A へ SSO → SP-A セッション開始(IdP に依存しない Cookie)
t2: SP-B へ SSO → 再認証なしでアサーション発行(IdP セッションが生きている)
t3: SP-A から「ログアウト」 → SP-A セッションだけ破棄
→ IdP セッションも SP-B セッションも生きたまま
ここに落とし穴があります。SP-A でログアウトしても IdP セッションが残っていれば、SP-A に再アクセスした瞬間に再 SSO されて即ログインし直されることがあります。「ログアウトしたのに入れてしまう」体感の正体はこれです。全体を確実に終わらせるには SLO(シングルログアウト) が要ります。
SLO は IdP が LogoutRequest をログイン中の全 SP へ順に送り、各 SP のローカルセッションを破棄してから IdP セッションを終了させる仕組みです。試験・実務で問われるのは「どこか1つの SP が応答しない/SLO 未対応だと、そのセッションが残存し全体ログアウトが完全に成立しない」という弱点です。LogoutRequest/LogoutResponse も署名対象であり、ログアウトの偽装(強制ログアウト DoS)を防ぐために検証が必要です。
まとめ:信頼の連鎖は署名検証に集約される
SAML SSO は「IdP に認証を集約し、SP は署名付きアサーションを検証するだけ」という明快なモデルです。だからこそ、安全性は署名検証の正しさ——正しい鍵・正しい範囲・正しいアルゴリズム——にほぼ全面的に依存します。XML Signature Wrapping が示すのは、署名アルゴリズムがいくら強固でも、XML 処理層で「検証した要素」と「使った要素」がずれれば防御は崩れるという教訓です。フローの起点(SP-Initiated を基本に)、リプレイ対策(ID キャッシュと有効期限)、そして2層セッションと SLO の伝播——この4点を押さえれば、SSO 連携の事故の大半は未然に防げます。チケットベースの SSO と対比したい場合はKerberos の内部も合わせて読むと、署名方式とチケット方式の設計思想の違いがつかめます。
セキュリティ Article
SAML と SSO の内部(アサーションと署名検証)を実務で読む
TL;DRは入口です。実際に選ぶ・使う段階では、何を解決するか、何と比較するか、導入後にどこで詰まるかまで見る必要があります。
解決すること
SAML
比較で見る軸
難易度: advanced / カテゴリ: セキュリティ / タグ数: 5
導入後に効く点
署名検証は「正しい鍵で・正しい範囲に・正しいアルゴリズムで」署名されているかの3点が要。XML Signature Wrapping は『署名された要素』と『SP が実際に読む要素』をずらして攻撃する。
先に潰すリスク
用語だけ覚えても、設計・実装・運用でどこに効くかを確認しないと判断を誤る。
- 難易度
- advanced
- カテゴリ
- セキュリティ
- タグ数
- 5
判断チェックリスト
- 自社の用途が「SAML / シングルサインオン」に近いか確認する。
- 強みである「SAML SSO は IdP が発行した署名付きアサーションを SP が検証する仕組み。SP-Initiated は SP の AuthnRequest が起点、IdP-Initiated は IdP のポータルが起点で、後者は未要求の応答を受け入れるため CSRF 耐性が低い。」が本当に評価軸になるか確認する。
- 注意点の「用語だけ覚えても、設計・実装・運用でどこに効くかを確認しないと判断を誤る。」を運用で吸収できるか確認する。
- 公開値や仕様値は、対象プラン・対象機種・対象リージョンまで確認する。
- 既存システム、ID、ネットワーク、監視、バックアップとの接続方法を先に洗い出す。
- 小さく試してから、本番移行、権限設計、障害時手順、コスト監視を決める。