FIDO2 がフィッシング耐性がある認証 (MFA) と呼ばれる理由

# はじめに
AiTM や Pass-the-Cookie と呼ばれるような **MFA をバイパスする攻撃への対策として FIDO2 認証が有効** という話を聞いたことがある方も多いと思います。

上記のような高度なフィッシングは認証要求をリアルタイムでプロキシする方法をとるのですが、私自身は今まで「PKI ベースの認証の場合は認証要求や応答をプロキシ出来ない何らかの技術的な仕組みがあるのだろう」というざっくりとした理解をしていました。

たまたま FIDO2 認証をテストしているうちに、今回個人的にはじめて分かったこともあったので、FIDO2 がなぜフィッシング耐性があると言われるのかについてブログにまとめたいと思います。
今まで私と同じようにふわっと理解していた方の助けになれば幸いです。

# そもそも MFA ってバイパスできるの？
MFA ってフィッシング対策でしょ？てことはフィッシング耐性があるのでは？と疑問に思われた方もいると思いますが、その考えはある意味正しいです。マイクロソフトも様々なイベントやドキュメントで **MFA によって 99.9% の攻撃を防げる** と言っています。

ただし、技術的には SMS や電話を使ったタイプの MFA はバイパスが可能であり、それが冒頭に書いた AiTM や Pass-the-cookie と呼ばれる攻撃手法です。

これらの手口は決して新しいものではなく、私が知る限り 2017 年ころには既に存在しており、私自身も Microsoft Digital Trust Summit 2019 (https://www.youtube.com/watch?v=TlPSEN53_NI) でこの攻撃手法と対策について紹介したことがあります。

これらの攻撃は 2021 年頃から大規模なキャンペーンが確認されるようになり、ブラックマーケットでの攻撃ツールの流通も手伝い、今後もその脅威はますます拡大していくと予想できます。

MFA を適用していれば 99.9% 大丈夫というのはその通りだと思いますが、それはパスワード スプレーなどのかなり大規模に自動化された攻撃を含めたマクロの話です。
ソーシャル エンジニアリングと組み合わせるなどした場合にはかなり高い確率で MFA がバイパスされてしまうと思いますので、今後認証セキュリティを考えていく上では対策は必須になると思います。

参考 URL：
https://www.microsoft.com/en-us/security/blog/2022/07/12/from-cookie-theft-to-bec-attackers-use-aitm-phishing-sites-as-entry-point-to-further-financial-fraud/
https://www.microsoft.com/en-us/security/blog/2023/06/08/detecting-and-mitigating-a-multi-stage-aitm-phishing-and-bec-campaign/

# フィッシング耐性がある MFA
これまで説明してきた MFA をバイパスする攻撃ですが、実はある種の MFA を使用することで防ぐことができ、それらは **フィッシング耐性がある MFA (Phishing-resistant MFA)** と呼ばれます。

Entra ID 条件付きアクセスでも**認証強度 (Authentication Strengths)** という機能の中で明確に区別しています。
https://learn.microsoft.com/en-us/entra/identity/authentication/concept-authentication-strengths#built-in-authentication-strengths

簡単に説明すると、下記の MFA はフィッシング耐性があるということになっています。
- FIDO2 セキュリティ キー (今はパスキーと呼びますがドキュメントに合わせます)
- Windows Hello for Business
- 証明書認証

この後、FIDO2 セキュリティ キーがどのような理由でフィッシング耐性があると言われているのかを見ていきたいと思います。

# MFA をバイパスする方法
FIDO2 が MFA バイパスに対してどのように有効かの話をする前に、まずは一般的な MFA がどのようにバイパスされるかを簡単にまとめます。

```mermaid
sequenceDiagram
    participant MFAEndpoint as MFA エンドポイント
    participant Victim as 被害者ユーザー (デバイス、ブラウザ)
    participant AttackerProxy as 攻撃者のプロキシサーバー (login.attacker.com)
    participant EntraID as Entra ID (login.microsoftonline.com)

    AttackerProxy->>Victim: フィッシングなどで login.attacker.com のリンクを踏ませる
    Note over Victim: リンクをクリックする
 
    Victim->>AttackerProxy: GET https://login.attacker.com
    Note over AttackerProxy: リクエストの書き換え
    AttackerProxy->>EntraID: GET https://login.microsoftonline.com
 
    Note over EntraID: ログイン ページの生成
    EntraID->>AttackerProxy: ログイン ページ (login.microsoftonline.com)
    Note over AttackerProxy: レスポンスの書き換え
    AttackerProxy->>Victim: ログイン ページ (login.attacker.com)
 
    Note over Victim: ユーザー ID とパスワードの入力
    Victim->>AttackerProxy: POST https://login.attacker.com (パスワード)
    Note over AttackerProxy: パスワード入手
    Note over AttackerProxy: リクエストの書き換え
    AttackerProxy->>EntraID: POST https://login.microsoftonline.com (パスワード)
 
    Note over EntraID: パスワード認証 OK
    EntraID-->>MFAEndpoint: MFA チャレンジ リクエスト
    MFAEndpoint->>Victim: MFA チャレンジ
 
    Note over Victim: MFA 応答
    Victim->>MFAEndpoint: MFA 応答
    MFAEndpoint-->>EntraID: MFA チャレンジ完了通知
    EntraID-->>Victim: 認証完了通知
 
    Note over Victim: 自動遷移
    Victim->>AttackerProxy: POST https://login.attacker.com
    AttackerProxy->>EntraID: POST https://login.microsoftonline.com
 
    Note over EntraID: セッション Cookie 発行
    EntraID->>AttackerProxy: セッション Cookie (login.microsoftonline.com)
    Note over AttackerProxy: セッション Cookie 入手
    Note over AttackerProxy: セッション Cookie 利用
    AttackerProxy->>EntraID: Microsoft 365 などへアクセス
```

1. 攻撃者はプロキシ サーバー (説明のために login.attacker.com とします) を用意し、被害者に成りすましメールを送るなどしてそのリンクを踏ませます。
2. 被害者からプロキシ サーバーに届いた HTTP GET リクエストのヘッダーやボディに含まれる login.attacker.com 部分を Entra ID の正規の URL (login.microsoftonline.com) に書き換えて Entra ID にリクエストします。
つまり、この時点で「被害者と攻撃者間」「攻撃者と Entra ID 間」の 2 つの TLS セッションが確立されており、Entra ID から見ると攻撃者のプロキシ サーバーが正規のクライアントに見えています。
3. Entra ID はプロキシ サーバーにログイン ページを返し、今度は login.microsoftonline.com 部分を login.attacker.com に書き換えて被害者にレスポンスを返します。
被害者から見るとプロキシ サーバーが正規の Entra ID に見えています。(URL 見れば違いがすぐ分かるのですが)
4. 被害者はログイン ページにユーザー ID とパスワードを入力して「サインイン」をクリックします。
5. プロキシ サーバーは上記と同様に login.attacker.com と login.microsoftonline.com に書き換えて Entra ID にユーザー ID とパスワードを送信します。
この時点でパスワードは攻撃者に渡ってしまった状態です。
6. Entra ID 側でパスワードの認証が通った後、(設定されていれば・・) MFA を求めます。
この MFA 要求は上記の経路とは別で行われます。つまり、Entra ID の MFA を担当する正規のエンドポイントと被害者間で直接行われます。
7. 被害者が MFA に応答したら、被害者からプロキシ サーバーに、プロキシ サーバーから Entra ID にセッション Cookie の発行リクエストが POST されます。
8. Entra ID はセッション Cookie を発行し、プロキシ サーバーに返します。
この時点で、セッション Cookie が攻撃者に奪われ、Microsoft 365 などの Entra ID で認証する全てのアプリケーションへのアクセスが可能になります。

上記の 7. 8. の HTTP POST リクエストとレスポンスはこのような感じになります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/563573/c2bde5b9-fe0e-4b22-3c80-8a6d0e211875.png)

赤で塗りつぶしている部分は上記の説明の例でいうと login.attacker.com で、クライアントは攻撃者のプロキシ サーバーに HTTP POST していることが分かります。
また、プロキシ サーバーからのレスポンスには Entra ID の認証が通ったことを示す Cookie が含まれていることが分かります。
ちなみに、黒で塗りつぶしている部分にはユーザー ID (UserPrincipalName) が含まれます。

# FIDO2 の認証フロー
上記で説明した例は "フィッシング耐性がない" MFA を使用した例ですが、FIDO2 セキュリティ キーを使用した場合はこのようになります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/563573/b620c525-29ff-bcde-2e3b-8a6acde84221.png)

まず目につくのは POST する先の URL が攻撃者のプロキシ サーバーではなく、login.microsoft.com になっており、直接正規の認証サーバーと通信していることが分かります。
また、レスポンスはエラー ID 135004 (Invalid postBackUrl parameter) のメッセージが確認でき、ユーザーには下記のように表示され、認証が失敗したことが確認できます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/563573/ff5d612f-0a58-483d-3f09-1d02c9c40d54.png)

この結果を最初見たときに、FIDO2 認証の場合は認証サーバーの URL が異なる (login.microsoftonline.com ではなく login.microsoft.com になっている) ので、プロキシ サーバー側で文字列をリプレースする設定が漏れているのではないかと思いました。

しかし、パケット キャプチャ内を色々探してみたのですが、サーバーからのレスポンスに login.microsoft.com やそれをエンコードしたような値はどこにも含まれていませんでした。
レスポンスに login.microsoft.com が含まれていれば、これを login.microsoft.com.attacker.com などに変換すれば一次的には突破できそうですが、レスポンスに含まれていなければそのようなことも出来ません。

認証サーバーからドメイン情報を受け取っていなくても FIDO2 認証時に下記のようにドメインが表示されていますので、少なくとも私がテストしたシナリオではドメイン情報は最初からセキュリティ キーに保存されているように思われます。(ドメイン情報をどこから取得しているのか、どなたか詳しい方いたら教えてください)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/563573/45d9e86f-4d33-bfee-3109-cab288c6eda1.png)

# FIDO2 認証の肝は rpId
色々調べてみると、FIDO2 では rpId という情報を使って認証サーバーのドメインを厳密に扱う仕様があるようです。
テスト時のイベント ログ (アプリケーションとサービスログ > Microsoft > Windows > WebAuthN) を見ると、イベント ID 1104 (Cbor Decode GetAssertion Response) が見つかります。 

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/563573/2f72665f-1e9e-e988-5c4d-01f7a4b00a00.png)

イベント ログ中にある GetAssertion の「Assertion」とは認証時に認証サーバーから送られてきたチャレンジや認証サーバー (RP、今回の場合 Entra ID) のドメイン情報などをセキュリティ キー (FIDO2 では認証器、Authenticator という) 内の秘密鍵で署名したものをいうそうです。
RpIdHash は 356C9ED4A09321B9695F1EAF918203F1B55F689DA61FBC96184C157DDA680C81 という文字列が入っていますが、これを CBOR 形式でデコードすると login.microsoft.com という文字列になります。
チャレンジだけではなく RpIdHash (prId) も一緒に秘密鍵で署名してデータを認証サーバーに送付し、認証サーバーはそれを公開鍵で検証して rpId が login.microsoft.com と等しいかをチェックできる仕組みになっているようです。

このような仕組みになっているので、仮に秘密鍵で署名する前の処理に関与して rpId 情報を login.attacker.com に書き換え出来たとしてもセキュリティ キーに保存されているドメイン情報とマッチしないため認証できず、秘密鍵で署名後のデータにアクセスできたとしても rpId を編集できない (公開鍵がないと検証できず、仮に公開鍵を入手できて検証できても秘密鍵がないと署名出来ない) ので、プロキシ サーバーを使用したリアルタイム フィッシング攻撃は成立しない、という結論になります。

参考ブログ：
https://blog.haniyama.com/2019/07/15/azuread-fido2-security-key-public-preview/
https://gihyo.jp/dev/column/newyear/2019/webauthn
https://engineering.mercari.com/blog/entry/2019-06-04-120000/

# まとめ
最初は「PKI ベースの認証の場合は認証要求や応答をプロキシ出来ない何らかの技術的な仕組みがあるのだろう」くらいの理解でしたが、FIDO2 には認証サーバーのドメインを厳密に扱う仕組みがあることで MFA をバイパスする攻撃に対する耐性があることが改めて確認できました。

FIDO2 認証については素人なので、用語の使い方がおかしかったり理解が間違っている部分がありましたらご指摘いただけると幸いです。

今回は FIDO2 を取り上げましたが、Windows Hello for Business や証明書認証についてもどのような理由でフィッシング耐性があると言われているのかも分かったら改めてブログにまとめたいと思います。