<p><!--META
{
"title": "FIDO2/WebAuthnによるパスワードレス認証の実装におけるセキュリティプラクティス",
"primary_category": "セキュリティ>認証",
"secondary_categories": ["FIDO2","WebAuthn"],
"tags": ["FIDO2","WebAuthn","パスワードレス","認証","公開鍵暗号","セキュリティキー"],
"summary": "FIDO2/WebAuthnパスワードレス認証実装における脅威モデル、攻撃シナリオ、検出/緩和策、運用上の落とし穴について解説。",
"mermaid": true,
"verify_level": "L0",
"tweet_hint": {"text":"FIDO2/WebAuthnパスワードレス認証のセキュリティ実装ガイド。脅威モデルから攻撃シナリオ、Relying Party側の検証、鍵管理、運用課題まで、安全なシステム構築のための実践的な知見を提供します。 #FIDO2 #WebAuthn #セキュリティ","hashtags":["#FIDO2","#WebAuthn","#セキュリティ"]},
"link_hints": [
"https://www.w3.org/TR/webauthn-1/",
"https://fidoalliance.org/fido2/",
"https://nvlpubs.nist.gov/nistpubs/SpecialPublications/NIST.SP.800-63b.pdf"
]
}
-->
本記事は<strong>Geminiの出力をプロンプト工学で整理した業務ドラフト(未検証)</strong>です。</p>
<h1 class="wp-block-heading">FIDO2/WebAuthnによるパスワードレス認証の実装におけるセキュリティプラクティス</h1>
<p>FIDO2とWebAuthnは、パスワードに依存しないセキュアな認証メカニズムをウェブに提供するための標準規格です。従来のパスワード認証に潜むフィッシング、パスワードリスト攻撃、ブルートフォース攻撃といった脅威に対して、公開鍵暗号の原則に基づき堅牢な防御を提供します。しかし、その強力なセキュリティを最大限に引き出すためには、Relying Party (RP) 側の適切な実装と運用が不可欠です。本記事では、FIDO2/WebAuthnの実装における脅威モデル、攻撃シナリオ、検出・緩和策、そして運用上の注意点について、セキュリティエンジニアの視点から解説します。</p>
<h2 class="wp-block-heading">脅威モデル</h2>
<p>FIDO2/WebAuthnは、多くの従来の認証脅威に対して強力な耐性を持ちますが、 RP側の実装不備や認証器の管理不備により新たなリスクが生じる可能性があります。主要な脅威モデルは以下の通りです。</p>
<ol class="wp-block-list">
<li><p><strong>フィッシング攻撃</strong>: WebAuthnは「Originの固定(RP ID)」と「ユーザー検証」により、従来のパスワードフィッシングに対して高い耐性を持ちます[1]。しかし、RP側がOrigin検証を怠る、または認証器がPIN/生体認証などのユーザー検証を要求しない場合、その耐性は低下します。</p></li>
<li><p><strong>中間者攻撃 (Man-in-the-Middle, MitM)</strong>: WebAuthnはTLS/HTTPSの使用を前提としているため、通信経路上のMitM攻撃に対してはTLSが提供する保護に依存します。しかし、TLSの検証不備や証明書の詐称により、MitMが成立する可能性はゼロではありません。</p></li>
<li><p><strong>リプレイ攻撃</strong>: 認証時に利用されるチャレンジ(<code>challenge</code>)は、RPが生成する一回限りの乱数データであり、これにより認証応答(<code>Assertion</code>)のリプレイを防止します[2]。RP側がチャレンジのワンタイム性を厳格に管理しない場合、攻撃者が過去の有効な認証応答を再利用して認証を突破するリスクがあります。</p></li>
<li><p><strong>認証器の盗難/紛失</strong>: 認証器(セキュリティキー、スマートフォンの生体認証など)が盗難または紛失した場合、ユーザー検証機能(PIN、生体認証)がない、または脆弱な認証器では、認証器を所持した攻撃者がそのまま認証を突破する可能性があります[3]。</p></li>
<li><p><strong>認証器の偽装</strong>: 不正な認証器がシステムに登録されるリスクです。Attestation(認証器の正当性証明)メカニズムにより、特定の認証器が信頼できるものであることを確認できますが、Attestationを適切に検証しない場合、セキュリティが低下します[2]。</p></li>
</ol>
<h2 class="wp-block-heading">攻撃シナリオ</h2>
<p>ここでは、RP側の実装不備を突くフィッシング攻撃のシナリオをMermaidのAttack Chainで示します。</p>
<div class="wp-block-merpress-mermaidjs diagram-source-mermaid"><pre class="mermaid">
graph TD
A["攻撃者: フィッシングサイト構築 (偽RP)"] --> B["攻撃者: ユーザーを偽RPへ誘導"];
B --> C{"ユーザー: 偽RPにアクセスし認証要求"};
C --> D["偽RP: 攻撃者が準備したChallengeを送信|通常は一意なChallengeを生成すべき|"];
D --> E{"ユーザー: 認証器でChallengeに署名し偽RPへ返送"};
E --> F["偽RP: 署名済みAssertionを攻撃者に渡す|Origin検証を意図的に怠る|"];
F --> G["攻撃者: 本物RPに Assertionを送信し認証成功|Challenge再利用やOrigin検証の欠陥を悪用|"];
G --> H["攻撃者: ユーザーとしてシステムへアクセス"];
</pre></div>
<p><em>解説</em>:
このシナリオでは、攻撃者は正規のサイトに見せかけたフィッシングサイト(偽RP)を構築します。ユーザーが偽RPで認証を試みると、攻撃者が制御する偽RPは、Challengeの生成やOrigin検証といったWebAuthnプロトコルのセキュリティ要件を意図的に無視します。具体的には、Challengeを使い回したり、RP IDのOrigin検証を怠ったりすることで、ユーザーの認証器が生成した署名済みAssertionを傍受し、それを正規のRPに送信することで認証を突破します。これにより、WebAuthnが本来持つフィッシング耐性が失われます。</p>
<h2 class="wp-block-heading">検出/緩和策</h2>
<p>FIDO2/WebAuthnのセキュリティを確保するためには、RP側で以下の厳格な検証と管理を実装する必要があります。</p>
<h3 class="wp-block-heading">1. Relying Party (RP) 側の厳格な検証</h3>
<p>認証応答(Assertion)の検証は、FIDO2/WebAuthnのセキュリティの要です。以下の項目を確実に検証する必要があります。</p>
<ul class="wp-block-list">
<li><p><strong>チャレンジ(<code>challenge</code>)の検証</strong>:</p>
<ul>
<li><p>サーバーが生成した<strong>一意</strong>のチャレンジと、Assertionに含まれるチャレンジが<strong>一致</strong>すること。</p></li>
<li><p>そのチャレンジが<strong>一度しか使用されていない</strong>こと(リプレイ攻撃防止)。</p></li>
<li><p>チャレンジが<strong>適切な時間内に消費された</strong>こと(有効期限)。</p></li>
<li><p><strong>誤用例</strong>: 毎回同じ固定値のチャレンジを使用する、またはチャレンジの使い回しを許容する。</p></li>
<li><p><strong>安全な代替</strong>: セッションごとに暗号論的強度を持つ乱数を生成し、RP側のデータベースやセッションストアでチャレンジを追跡し、使用後に無効化する。
<div class="codehilite">
<pre data-enlighter-language="generic">import os
import hashlib
from datetime import datetime, timedelta</pre></div></p></li>
</ul>
<p><span class="c1"># 安全な代替: Challengeの生成</span></p>
<p><span class="k">def</span><span class="w"> </span><span class="nf">generate_secure_challenge</span><span class="p">():</span>
<span class="n">challenge</span> <span class="o">=</span> <span class="n">os</span><span class="o">.</span><span class="n">urandom</span><span class="p">(</span><span class="mi">32</span><span class="p">)</span> <span class="c1"># 暗号論的強度を持つランダムな32バイト</span></p>
<p><span class="c1"># チャレンジと有効期限をセッションまたはDBに保存</span></p>
<p><span class="c1"># 例: session[‘challenge’] = challenge.hex(), session[‘challenge_expiry’] = datetime.now() + timedelta(minutes=5)</span></p>
<p><span class="k">return</span> <span class="n">challenge</span></p>
<p><span class="c1"># 安全な代替: Challengeの検証(認証要求時)</span></p>
<p><span class="k">def</span><span class="w"> </span><span class="nf">verify_challenge</span><span class="p">(</span><span class="n">received_challenge_hex</span><span class="p">,</span> <span class="n">stored_challenge_hex</span><span class="p">,</span> <span class="n">stored_expiry_time</span><span class="p">):</span>
<span class="k">if</span> <span class="n">received_challenge_hex</span> <span class="o">!=</span> <span class="n">stored_challenge_hex</span><span class="p">:</span>
<span class="k">return</span> <span class="kc">False</span><span class="p">,</span> <span class="s2">“Challenge mismatch”</span>
<span class="k">if</span> <span class="n">datetime</span><span class="o">.</span><span class="n">now</span><span class="p">()</span> <span class="o">></span> <span class="n">stored_expiry_time</span><span class="p">:</span>
<span class="k">return</span> <span class="kc">False</span><span class="p">,</span> <span class="s2">“Challenge expired”</span></p>
<p><span class="c1"># challengeを消費済みとして無効化(例: session.pop(‘challenge’), session.pop(‘challenge_expiry’))</span></p>
<p><span class="k">return</span> <span class="kc">True</span><span class="p">,</span> <span class="s2">“Challenge valid”</span></p>
<p><span class="c1"># 誤用例: チャレンジの固定値や再利用は厳禁</span></p>
<p><span class="c1"># FIXED_CHALLENGE = b”some_static_challenge_value” # 誤用例</span>
</p></li>
<li><p><strong>オリジン(<code>rpId</code>)の検証</strong>:</p>
<ul>
<li><p>Assertionに含まれるRP ID(<code>rpIdHash</code>または<code>rpId</code>)が、<strong>期待されるRPのオリジン</strong>と一致すること。これにより、フィッシングサイトが正規の認証器から署名を得ても、それを正規サイトで利用できないようにします。</p></li>
<li><p><strong>誤用例</strong>: <code>rpId</code>の検証を全く行わない、またはユーザーがアクセスしたURLと直接比較せず、部分的な文字列マッチングで済ませる。</p></li>
<li><p><strong>安全な代替</strong>: RPが信頼するドメインリストと、受け取ったAssertion内のRP IDを厳格に照合する。
<div class="codehilite">
<pre data-enlighter-language="generic">import hashlib</pre></div></p></li>
</ul>
<p><span class="c1"># 安全な代替: RP IDの検証</span></p>
<p><span class="k">def</span><span class="w"> </span><span class="nf">verify_rp_id</span><span class="p">(</span><span class="n">auth_data_rp_id_hash</span><span class="p">,</span> <span class="n">expected_rp_id</span><span class="p">):</span></p>
<p><span class="c1"># auth_data_rp_id_hash はAuthenticatorDataから抽出されたRP ID Hash</span></p>
<p><span class="c1"># expected_rp_id はRPのドメイン名 (例: “example.com”)</span></p>
<p><span class="n">expected_rp_id_hash</span> <span class="o">=</span> <span class="n">hashlib</span><span class="o">.</span><span class="n">sha256</span><span class="p">(</span><span class="n">expected_rp_id</span><span class="o">.</span><span class="n">encode</span><span class="p">(</span><span class="s1">‘utf-8’</span><span class="p">))</span><span class="o">.</span><span class="n">digest</span><span class="p">()</span>
<span class="k">if</span> <span class="n">auth_data_rp_id_hash</span> <span class="o">!=</span> <span class="n">expected_rp_id_hash</span><span class="p">:</span>
<span class="k">return</span> <span class="kc">False</span><span class="p">,</span> <span class="s2">“RP ID mismatch”</span>
<span class="k">return</span> <span class="kc">True</span><span class="p">,</span> <span class="s2">“RP ID valid”</span></p>
<p><span class="c1"># 誤用例: rpIdHashの検証を省略する、または固定値と比較しない</span></p>
<p><span class="c1"># if auth_data_rp_id_hash.startswith(b”some_prefix”): # 誤用例 (厳格ではない)</span>
</p></li>
<li><p><strong>署名検証</strong>: 認証器から送られてきた署名(<code>signature</code>)が、登録されている公開鍵と一致することを確認する。</p></li>
<li><p><strong>フラグ(<code>flags</code>)の検証</strong>: <code>User Present (UP)</code>、<code>User Verified (UV)</code>などのフラグが期待通りであるか確認する。特に、パスワードレス認証では<code>UV</code>フラグが重要です。</p></li>
<li><p><strong>カウンター(<code>signCount</code>)の検証</strong>: リプレイ攻撃対策として、<code>signCount</code>の値が前回の認証時よりも増加していることを確認する。</p></li>
</ul>
<h3 class="wp-block-heading">2. 鍵/秘匿情報の安全な取り扱いとライフサイクル管理</h3>
<ul class="wp-block-list">
<li><p><strong>公開鍵とCredential IDの保存</strong>: 認証器が登録時にRPに送付する公開鍵とCredential IDは、ユーザーごとに紐付けてデータベースに安全に保存する必要があります。これらは暗号化された環境に保管し、アクセス権限を最小限に制限します。</p></li>
<li><p><strong>鍵のローテーション</strong>: WebAuthnの公開鍵は認証器が生成するため、RP側で直接「ローテーション」する概念は薄いです。代わりに、ユーザーが新しい認証器を登録し、古い認証器を削除することで実質的なローテーションを実現します。定期的な認証器の棚卸しと、長期間未使用の認証器の無効化を検討します。</p></li>
<li><p><strong>最小権限の原則</strong>: 認証情報(公開鍵、Credential ID)にアクセスできるシステムや担当者を限定し、最小限の権限のみを付与します。</p></li>
<li><p><strong>監査</strong>: 認証器の登録、解除、認証成功、認証失敗などのイベントをすべて監査ログに記録します。不審な試行や異常なパターンを検出するための基盤となります。</p></li>
</ul>
<h2 class="wp-block-heading">運用対策</h2>
<p>安全な実装だけでなく、運用面でもFIDO2/WebAuthnの利点を最大限に引き出すための対策が必要です。</p>
<h3 class="wp-block-heading">1. 認証器のライフサイクル管理</h3>
<ul class="wp-block-list">
<li><p><strong>登録と解除プロセス</strong>: ユーザーが認証器を簡単かつセキュアに登録・解除できるインターフェースを提供します。解除時には、多要素認証などを用いて本人確認を厳格に行うべきです。</p></li>
<li><p><strong>リカバリープラン</strong>: 認証器の紛失や破損に備え、アカウントリカバリーの仕組みを準備します。このリカバリープロセス自体が攻撃対象とならないよう、複数の検証要素(バックアップ認証器、信頼済みのメールアドレス/電話番号へのワンタイムパスワード、本人確認書類の提出など)を組み合わせるなど、慎重に設計する必要があります。</p></li>
<li><p><strong>緊急時の対応</strong>: 不審な認証器の登録や、認証器が盗難されたと疑われる場合、速やかにその認証器を無効化し、ユーザーに通知する体制を確立します。</p></li>
</ul>
<h3 class="wp-block-heading">2. 監査とモニタリング</h3>
<p>認証の成功/失敗、認証器の登録/解除、不正アクセス試行などのログを詳細に記録し、SIEMなどのツールでリアルタイムにモニタリングします。異常な地理からのアクセス、短時間での複数回失敗、特定ユーザーからの異常な認証試行などを検知することで、攻撃の早期発見に繋がります。</p>
<h3 class="wp-block-heading">3. 現場の落とし穴とトレードオフ</h3>
<ul class="wp-block-list">
<li><p><strong>誤検知と検出遅延</strong>: RP側の検証ロジックが過度に厳しすぎると、正当な認証リクエストが誤って拒否される「誤検知」が発生する可能性があります。逆に緩すぎると「検出遅延」を招き、攻撃を許してしまいます。適切なバランスを見つけるためのテストと調整が重要です。</p></li>
<li><p><strong>可用性とのトレードオフ</strong>: 高いセキュリティ要件は、しばしばユーザーエクスペリエンスやシステム可用性とのトレードオフになります。認証器の紛失や故障時のリカバリープロセスが複雑すぎると、ユーザーの不満を招き、ヘルプデスクへの問い合わせが増加します。複数の認証器の登録を推奨したり、分かりやすいリカバリーガイドを提供したりするなどの工夫が必要です。</p></li>
<li><p><strong>既存システムとの連携</strong>: 既存のユーザー管理システムや多要素認証(MFA)システムとの円滑な連携も課題です。FIDO2/WebAuthnを導入する際、既存のID/パスワード認証をどう移行・統合するか、慎重な計画が求められます。</p></li>
</ul>
<h2 class="wp-block-heading">まとめ</h2>
<p>FIDO2/WebAuthnは、パスワードレス認証の未来を切り拓く強力な技術ですが、その真価はRP側の堅牢な実装と運用によって発揮されます。チャレンジのワンタイム性、Originの厳格な検証、公開鍵やCredential IDの安全な管理、そして認証器のライフサイクル管理と監査がセキュリティの要となります。現場の落とし穴に注意し、セキュリティと可用性のバランスを取りながら、継続的な改善を行うことで、より安全で便利な認証体験をユーザーに提供できるでしょう。</p>
<hr/>
<p>[1] W3C Recommendation. “Web Authentication: An API for accessing Public Key Credentials Level 1”. W3C. 2019年3月4日.
[2] FIDO Alliance. “FIDO2 Overview”. FIDO Alliance. 最終更新日不明.
[3] National Institute of Standards and Technology (NIST). “Special Publication 800-63B: Digital Identity Guidelines, Authentication and Lifecycle Management”. NIST. 2017年6月20日.</p>
本記事はGeminiの出力をプロンプト工学で整理した業務ドラフト(未検証)です。
FIDO2/WebAuthnによるパスワードレス認証の実装におけるセキュリティプラクティス
FIDO2とWebAuthnは、パスワードに依存しないセキュアな認証メカニズムをウェブに提供するための標準規格です。従来のパスワード認証に潜むフィッシング、パスワードリスト攻撃、ブルートフォース攻撃といった脅威に対して、公開鍵暗号の原則に基づき堅牢な防御を提供します。しかし、その強力なセキュリティを最大限に引き出すためには、Relying Party (RP) 側の適切な実装と運用が不可欠です。本記事では、FIDO2/WebAuthnの実装における脅威モデル、攻撃シナリオ、検出・緩和策、そして運用上の注意点について、セキュリティエンジニアの視点から解説します。
脅威モデル
FIDO2/WebAuthnは、多くの従来の認証脅威に対して強力な耐性を持ちますが、 RP側の実装不備や認証器の管理不備により新たなリスクが生じる可能性があります。主要な脅威モデルは以下の通りです。
フィッシング攻撃: WebAuthnは「Originの固定(RP ID)」と「ユーザー検証」により、従来のパスワードフィッシングに対して高い耐性を持ちます[1]。しかし、RP側がOrigin検証を怠る、または認証器がPIN/生体認証などのユーザー検証を要求しない場合、その耐性は低下します。
中間者攻撃 (Man-in-the-Middle, MitM): WebAuthnはTLS/HTTPSの使用を前提としているため、通信経路上のMitM攻撃に対してはTLSが提供する保護に依存します。しかし、TLSの検証不備や証明書の詐称により、MitMが成立する可能性はゼロではありません。
リプレイ攻撃: 認証時に利用されるチャレンジ(challenge)は、RPが生成する一回限りの乱数データであり、これにより認証応答(Assertion)のリプレイを防止します[2]。RP側がチャレンジのワンタイム性を厳格に管理しない場合、攻撃者が過去の有効な認証応答を再利用して認証を突破するリスクがあります。
認証器の盗難/紛失: 認証器(セキュリティキー、スマートフォンの生体認証など)が盗難または紛失した場合、ユーザー検証機能(PIN、生体認証)がない、または脆弱な認証器では、認証器を所持した攻撃者がそのまま認証を突破する可能性があります[3]。
認証器の偽装: 不正な認証器がシステムに登録されるリスクです。Attestation(認証器の正当性証明)メカニズムにより、特定の認証器が信頼できるものであることを確認できますが、Attestationを適切に検証しない場合、セキュリティが低下します[2]。
攻撃シナリオ
ここでは、RP側の実装不備を突くフィッシング攻撃のシナリオをMermaidのAttack Chainで示します。
graph TD
A["攻撃者: フィッシングサイト構築 (偽RP)"] --> B["攻撃者: ユーザーを偽RPへ誘導"];
B --> C{"ユーザー: 偽RPにアクセスし認証要求"};
C --> D["偽RP: 攻撃者が準備したChallengeを送信|通常は一意なChallengeを生成すべき|"];
D --> E{"ユーザー: 認証器でChallengeに署名し偽RPへ返送"};
E --> F["偽RP: 署名済みAssertionを攻撃者に渡す|Origin検証を意図的に怠る|"];
F --> G["攻撃者: 本物RPに Assertionを送信し認証成功|Challenge再利用やOrigin検証の欠陥を悪用|"];
G --> H["攻撃者: ユーザーとしてシステムへアクセス"];
解説:
このシナリオでは、攻撃者は正規のサイトに見せかけたフィッシングサイト(偽RP)を構築します。ユーザーが偽RPで認証を試みると、攻撃者が制御する偽RPは、Challengeの生成やOrigin検証といったWebAuthnプロトコルのセキュリティ要件を意図的に無視します。具体的には、Challengeを使い回したり、RP IDのOrigin検証を怠ったりすることで、ユーザーの認証器が生成した署名済みAssertionを傍受し、それを正規のRPに送信することで認証を突破します。これにより、WebAuthnが本来持つフィッシング耐性が失われます。
検出/緩和策
FIDO2/WebAuthnのセキュリティを確保するためには、RP側で以下の厳格な検証と管理を実装する必要があります。
1. Relying Party (RP) 側の厳格な検証
認証応答(Assertion)の検証は、FIDO2/WebAuthnのセキュリティの要です。以下の項目を確実に検証する必要があります。
チャレンジ(challenge)の検証:
サーバーが生成した一意のチャレンジと、Assertionに含まれるチャレンジが一致すること。
そのチャレンジが一度しか使用されていないこと(リプレイ攻撃防止)。
チャレンジが適切な時間内に消費されたこと(有効期限)。
誤用例: 毎回同じ固定値のチャレンジを使用する、またはチャレンジの使い回しを許容する。
安全な代替: セッションごとに暗号論的強度を持つ乱数を生成し、RP側のデータベースやセッションストアでチャレンジを追跡し、使用後に無効化する。
import os
import hashlib
from datetime import datetime, timedelta
# 安全な代替: Challengeの生成
def generate_secure_challenge():
challenge = os.urandom(32) # 暗号論的強度を持つランダムな32バイト
# チャレンジと有効期限をセッションまたはDBに保存
# 例: session[‘challenge’] = challenge.hex(), session[‘challenge_expiry’] = datetime.now() + timedelta(minutes=5)
return challenge
# 安全な代替: Challengeの検証(認証要求時)
def verify_challenge(received_challenge_hex, stored_challenge_hex, stored_expiry_time):
if received_challenge_hex != stored_challenge_hex:
return False, “Challenge mismatch”
if datetime.now() > stored_expiry_time:
return False, “Challenge expired”
# challengeを消費済みとして無効化(例: session.pop(‘challenge’), session.pop(‘challenge_expiry’))
return True, “Challenge valid”
# 誤用例: チャレンジの固定値や再利用は厳禁
# FIXED_CHALLENGE = b”some_static_challenge_value” # 誤用例
オリジン(rpId)の検証:
Assertionに含まれるRP ID(rpIdHashまたはrpId)が、期待されるRPのオリジンと一致すること。これにより、フィッシングサイトが正規の認証器から署名を得ても、それを正規サイトで利用できないようにします。
誤用例: rpIdの検証を全く行わない、またはユーザーがアクセスしたURLと直接比較せず、部分的な文字列マッチングで済ませる。
安全な代替: RPが信頼するドメインリストと、受け取ったAssertion内のRP IDを厳格に照合する。
# 安全な代替: RP IDの検証
def verify_rp_id(auth_data_rp_id_hash, expected_rp_id):
# auth_data_rp_id_hash はAuthenticatorDataから抽出されたRP ID Hash
# expected_rp_id はRPのドメイン名 (例: “example.com”)
expected_rp_id_hash = hashlib.sha256(expected_rp_id.encode(‘utf-8’)).digest()
if auth_data_rp_id_hash != expected_rp_id_hash:
return False, “RP ID mismatch”
return True, “RP ID valid”
# 誤用例: rpIdHashの検証を省略する、または固定値と比較しない
# if auth_data_rp_id_hash.startswith(b”some_prefix”): # 誤用例 (厳格ではない)
署名検証: 認証器から送られてきた署名(signature)が、登録されている公開鍵と一致することを確認する。
フラグ(flags)の検証: User Present (UP)、User Verified (UV)などのフラグが期待通りであるか確認する。特に、パスワードレス認証ではUVフラグが重要です。
カウンター(signCount)の検証: リプレイ攻撃対策として、signCountの値が前回の認証時よりも増加していることを確認する。
2. 鍵/秘匿情報の安全な取り扱いとライフサイクル管理
公開鍵とCredential IDの保存: 認証器が登録時にRPに送付する公開鍵とCredential IDは、ユーザーごとに紐付けてデータベースに安全に保存する必要があります。これらは暗号化された環境に保管し、アクセス権限を最小限に制限します。
鍵のローテーション: WebAuthnの公開鍵は認証器が生成するため、RP側で直接「ローテーション」する概念は薄いです。代わりに、ユーザーが新しい認証器を登録し、古い認証器を削除することで実質的なローテーションを実現します。定期的な認証器の棚卸しと、長期間未使用の認証器の無効化を検討します。
最小権限の原則: 認証情報(公開鍵、Credential ID)にアクセスできるシステムや担当者を限定し、最小限の権限のみを付与します。
監査: 認証器の登録、解除、認証成功、認証失敗などのイベントをすべて監査ログに記録します。不審な試行や異常なパターンを検出するための基盤となります。
運用対策
安全な実装だけでなく、運用面でもFIDO2/WebAuthnの利点を最大限に引き出すための対策が必要です。
1. 認証器のライフサイクル管理
登録と解除プロセス: ユーザーが認証器を簡単かつセキュアに登録・解除できるインターフェースを提供します。解除時には、多要素認証などを用いて本人確認を厳格に行うべきです。
リカバリープラン: 認証器の紛失や破損に備え、アカウントリカバリーの仕組みを準備します。このリカバリープロセス自体が攻撃対象とならないよう、複数の検証要素(バックアップ認証器、信頼済みのメールアドレス/電話番号へのワンタイムパスワード、本人確認書類の提出など)を組み合わせるなど、慎重に設計する必要があります。
緊急時の対応: 不審な認証器の登録や、認証器が盗難されたと疑われる場合、速やかにその認証器を無効化し、ユーザーに通知する体制を確立します。
2. 監査とモニタリング
認証の成功/失敗、認証器の登録/解除、不正アクセス試行などのログを詳細に記録し、SIEMなどのツールでリアルタイムにモニタリングします。異常な地理からのアクセス、短時間での複数回失敗、特定ユーザーからの異常な認証試行などを検知することで、攻撃の早期発見に繋がります。
3. 現場の落とし穴とトレードオフ
誤検知と検出遅延: RP側の検証ロジックが過度に厳しすぎると、正当な認証リクエストが誤って拒否される「誤検知」が発生する可能性があります。逆に緩すぎると「検出遅延」を招き、攻撃を許してしまいます。適切なバランスを見つけるためのテストと調整が重要です。
可用性とのトレードオフ: 高いセキュリティ要件は、しばしばユーザーエクスペリエンスやシステム可用性とのトレードオフになります。認証器の紛失や故障時のリカバリープロセスが複雑すぎると、ユーザーの不満を招き、ヘルプデスクへの問い合わせが増加します。複数の認証器の登録を推奨したり、分かりやすいリカバリーガイドを提供したりするなどの工夫が必要です。
既存システムとの連携: 既存のユーザー管理システムや多要素認証(MFA)システムとの円滑な連携も課題です。FIDO2/WebAuthnを導入する際、既存のID/パスワード認証をどう移行・統合するか、慎重な計画が求められます。
まとめ
FIDO2/WebAuthnは、パスワードレス認証の未来を切り拓く強力な技術ですが、その真価はRP側の堅牢な実装と運用によって発揮されます。チャレンジのワンタイム性、Originの厳格な検証、公開鍵やCredential IDの安全な管理、そして認証器のライフサイクル管理と監査がセキュリティの要となります。現場の落とし穴に注意し、セキュリティと可用性のバランスを取りながら、継続的な改善を行うことで、より安全で便利な認証体験をユーザーに提供できるでしょう。
[1] W3C Recommendation. “Web Authentication: An API for accessing Public Key Credentials Level 1”. W3C. 2019年3月4日.
[2] FIDO Alliance. “FIDO2 Overview”. FIDO Alliance. 最終更新日不明.
[3] National Institute of Standards and Technology (NIST). “Special Publication 800-63B: Digital Identity Guidelines, Authentication and Lifecycle Management”. NIST. 2017年6月20日.
コメント