FIDO2 セキュリティ考慮事項
概要
FIDO2/WebAuthn はパスワードに比べて大幅にセキュリティが向上しますが、適切に実装・運用しないとセキュリティ上の問題 が発生する可能性があります。
このドキュメントで学べること:
- FIDO2 が防御できる攻撃と防御できない攻撃
- signCount によるクローン検出
- セッション管理との統合
- 多要素認証との組み合わせ
- 実装上の注意点
FIDO2 の脅威モデル
FIDO2 が防御できる攻撃
┌─────────────────────────────────────────────────────────────────────────────┐
│ FIDO2 が防御できる攻撃 │
├──────────────────────────────────────────────────────────── ─────────────────┤
│ │
│ 【フィッシング攻撃】 ✓ 防御可能 │
│ ────────────────────────────────────────────────────────────────────────── │
│ 理由: 署名に RP の origin が含まれるため、偽サイトでは署名が無効 │
│ │
│ 攻撃者: 「偽の銀行サイト fake-bank.com にログインさせよう」 │
│ FIDO2: 署名の origin は "https://fake-bank.com" │
│ RP: origin が "https://real-bank.com" と一致しない → 拒否 │
│ │
│ 【パスワードリスト攻撃】 ✓ 防御可能 │
│ ────────────────────────────────────────────────────────────────────────── │
│ 理由: パスワードが存在しないため、流出したパスワードリストは無意味 │
│ │
│ 【ブルートフォース攻撃】 ✓ 防御可能 │
│ ────────────────────────────────────────────────────────────────────────── │
│ 理由: 秘密鍵は 256 ビット以上、総当たりは現実的に不可能 │
│ │
│ 【キーロガー】 ✓ 防御可能 │
│ ─────────────────────────────────────────────── ─────────────────────────── │
│ 理由: キーボード入力がないため、キーロガーでは認証情報を取得できない │
│ │
│ 【中間者攻撃(MITM)】 ✓ 防御可能 │
│ ────────────────────────────────────────────────────────────────────────── │
│ 理由: TLS + origin 検証により、中間者が介入しても署名が無効 │
│ │
│ 【リプレイ攻撃】 ✓ 防御可能 │
│ ────────────────────────────────────────────────────────────────────────── │
│ 理由: challenge がランダムで一度きり、同じ署名は再利用不可 │
│ │
└─────────────────────────────────────────────────────────────────────────────┘
FIDO2 が防御できない攻撃
┌─────────────────────────────────────────────────────────────────────────────┐
│ FIDO2 が防御できない攻撃 │
├─────────────────────────────────────────────────────────────────────────────┤
│ │
│ 【セッションハイジャック】 ✗ 防御不可 │
│ ────────────────────────────────────────────────────────────────────────── │
│ FIDO2 は認証の瞬間を保護するが、認証後のセッションは保護しない │
│ → セッショントークンが盗まれると攻撃者がなりすまし可能 │
│ 対策: セキュアなセッション管理(HttpOnly, Secure, SameSite) │
│ │
│ 【認証器の物理的盗難】 ✗ 防御不可 │
│ ────────────────────────────────────────────────────────────────────────── │
│ 認証器を盗まれた場合、攻撃者が認証可能になる可能性 │
│ 対策: UV(ユーザー検証)の要求、PIN/生体認証の設定 │
│ │
│ 【ソーシャルエンジニアリング】 ✗ 防御不可 │
│ ────────────────────────────────────────────────────────────────────────── │
│ ユーザーを騙して正規サイトで認証させる攻撃 │
│ 例: 「アカウント確認のため、ここをクリックしてログインしてください」 │
│ 対策: ユーザー教育、トランザクション確認 │
│ │
│ 【マルウェア(デバイス侵害)】 ✗ 防御不可 │
│ ────────────────────────────────────────────────────────────────────────── │
│ デバイス自体が侵害されている場合、あらゆる保護が無効化される可能性 │
│ 対策: エンドポイントセキュリティ、デバイス信頼度の評価 │
│ │
│ 【クローン攻撃(特定条件下)】 △ 部分的に防御可能 │
│ ────────────────────────────────────────────────────────────────────────── │
│ 認証器の秘密鍵がクローンされた場合 │
│ 対策: signCount によるクローン検出(後述) │
│ │
└─────────────────────────────────────────────────────────────────────────────┘
signCount によるクローン検出
signCount とは
┌─────────────────────────────────────────────────────────────────────────────┐
│ signCount の仕組み │
├─────────────────────────────────────────────────────────────────────────────┤
│ │
│ signCount は、認証器が署名を行うたびにインクリメントされるカウンター │
│ │
│ 【動作】 │
│ 1. 登録時: 認証器が signCount の初期値(通常 0 または 1)を返す │
│ 2. 認証時: 認証器が現在の signCount を返す │
│ 3. RP: 前回の signCount より大きいことを確認 │
│ │
│ 【正常なシーケンス】 │
│ │
│ 登録時 認証1回目 認証2回目 認証3回目 │
│ signCount=0 signCount=1 signCount=2 signCount=3 │
│ │ │ │ │ │
│ ▼ ▼ ▼ ▼ │
│ RP保存: 0 0 < 1 ✓ OK 1 < 2 ✓ OK 2 < 3 ✓ OK │
│ RP保存: 1 RP保存: 2 RP保存: 3 │
│ │
└─────────────────────────────────────────────────────────────────────────────┘
クローン検出
┌─────────────────────────────────────────────────────────────────────────────┐
│ クローン検出の仕組み │
├─────────────────────────────────────────────────────────────────────────────┤
│ │
│ 【シナリオ: 認証器がクローンされた場合】 │
│ │
│ 正規認証器: signCount = 5 で認証 │
│ RP: signCount = 5 を保存 │
│ │
│ クローン認証器: signCount = 3 で認証を試みる(クローン時点の値) │
│ RP: 保存値 5 > 受信値 3 → 異常を検出! │
│ │
│ 【検出パターン】 │
│ │
│ 時間軸 → │
│ │
│ 正規認証器: ──●────●────●────●────●──→ │
│ count=1 2 3 4 5 │
│ ↓ │
│ クローン作成 │
│ ↓ │
│ クローン: ─────●────●────●──→ │
│ count=3 4 5 │
│ │
│ RP での検出: │
│ - 正規が count=5 で認証 → RP は 5 を保存 │
│ - クローンが count=4 で認証 → 5 > 4 なので異常検出! │
│ │
└─────────────────────────────────────────────────────────────────────────────┘
signCount の実装
┌─────────────────────────────────────────────────────────────────────────────┐
│ signCount 検証の実装 │
├─────────────────────────────────────────────────────────────────────────────┤
│ │
│ 【検証ロジック】 │
│ │
│ if (storedSignCount > 0 || receivedSignCount > 0) { │
│ // どちらかが 0 より大きい場合、signCount をサポートしている │
│ if (receivedSignCount <= storedSignCount) { │
│ // 異常検出: クローンの可能性 │
│ handlePotentialClone(); │
│ } │
│ } │
│ // signCount を更新 │
│ updateStoredSignCount(receivedSignCount); │
│ │
│ 【異常検出時の対応オプション】 │
│ │
│ 1. 認証を拒否 │
│ - 最も安全だが、正当なユーザーをブロックする可能性 │
│ │
│ 2. 警告を出して認証は許可 │
│ - ユーザーに通知、追加の検証を要求 │
│ │
│ 3. ログに記録して監視 │
│ - 認証は許可するが、セキュリティチームに通知 │
│ │
│ 【注意】 │
│ - 一部の認証器は signCount をサポートしていない(常に 0) │
│ - 同期 Passkey は signCount が正確でない場合がある │
│ │
└─────────────────────────────────────────────────────────────────────────────┘
signCount の制限
| 認証器タイプ | signCount サポート | 備考 |
|---|---|---|
| ハードウェアキー(YubiKey等) | ✓ サポート | 正確にインクリメント |
| プラットフォーム認証器 | ✓ サポート | デバイスごとに管理 |
| 同期 Passkey | △ 不正確な場合あり | 同期により複数デバイスで値がずれる |
| 一部の古い認証器 | ✗ 非サポ ート | 常に 0 |
セッション管理との統合
認証後のセッション保護
┌─────────────────────────────────────────────────────────────────────────────┐
│ FIDO2 とセッション管理 │
├─────────────────────────────────────────────────────────────────────────────┤
│ │
│ 【FIDO2 が保護する範囲】 │
│ │
│ 認証リクエスト 認証処理 認証完了 セッション利用 │
│ │ │ │ │ │
│ ────────┼──────────────┼──────────────┼──────────────┼─────────→ │
│ │ │ │ │ │
│ └──────────────┴──────────────┘ │ │
│ FIDO2 の保護範囲 │ │
│ │ │
│ ここからは RP の責任 │
│ │
│ 【セッション管理のベストプラクティス】 │
│ │
│ 1. セッショントークンの保護 │
│ - HttpOnly: JavaScript からアクセス不可 │
│ - Secure: HTTPS 通信でのみ送信 │
│ - SameSite=Strict または Lax: CSRF 対策 │
│ │
│ 2. セッションの有効期限 │
│ - 適切なタイムアウト設定 │
│ - アイドルタイムアウト │
│ │
│ 3. セッション固定攻撃対策 │
│ - 認証成功後にセッション ID を再生成 │
│ │
└─────────────────────────────────────────────────────────────────────────────┘
重要操作時の再認証
┌─────────────────────────────────────────────────────────────────────────────┐
│ Step-up 認証(再認証) │
├─────────────────────────────────────────────────────────────────────────────┤
│ │
│ 【コンセプト】 │
│ 重要な操作を行う前に、再度 FIDO2 認証を要求する │
│ │
│ 【再認証を要求すべき操作の例】 │
│ - パスワードの変更 │
│ - メールアドレスの変更 │
│ - 新しい認証器の登録 │
│ - 高額の送金・決済 │
│ - アカウント削除 │
│ - 個人情報の表示・変更 │
│ │
│ 【実装例】 │
│ │
│ // セッションに最終認証時刻を保存 │
│ session.lastAuthTime = Date.now(); │
│ │
│ // 重要操作前にチェック │
│ function requireRecentAuth(maxAgeMinutes = 5) { │
│ const elapsed = Date.now() - session.lastAuthTime; │
│ if (elapsed > maxAgeMinutes * 60 * 1000) { │
│ // 再認証を要求 │
│ return redirectToReauth(); │
│ } │
│ } │
│ │
└─────────────────────────────────────────────────────────────────────────────┘