1. はじめに
興味深いウェブアプリケーションは、多くの場合、ユーザーに関する機微なデータを開示したり、ユーザーの代理で何らかのアクションを実行したりする可能性のある、多数のウェブで公開されるエンドポイントを持つことになります。ユーザーのブラウザは、それらのエンドポイントへのリクエストを簡単に行うよう誘導でき、その際にはユーザーの周辺認証情報(Cookieやイントラネットでの特権的な立場など)も含まれることから、アプリケーションはこれらのエンドポイントの取り扱いについて、悪用を防ぐために非常に注意深くある必要があります。
注意深くあることは、場合によっては困難(「単純な」CSRFなど)であり、他の場合(クロスサイト検索、タイミング攻撃など)には事実上不可能です。後者のカテゴリには、サーバー側の処理時間に基づくタイミング攻撃や、応答の長さによる測定(ウェブ側タイミング攻撃や受動的なネットワーク攻撃者によるもの)が含まれます。
サーバーが、そのリクエストの仕方に基づいて、応答するかどうかをより賢く判断できれば、こうした攻撃の一部を軽減できるでしょう。例えば、銀行サーバーの「すべてのお金を振り込む」エンドポイントが、imgタグから参照されることや、evil.comから正当なリクエストが発生することは非常に考えにくいでしょう。理想的には、サーバーはこれらのリクエストをアプリケーションバックエンドに処理させることなく、a priori(事前に)拒否できるのが望ましいです。
ここでは、ユーザーエージェントが送信リクエストに追加のコンテキストを付加することで、このような意思決定を可能にする仕組みを説明します。サーバーにフェッチメタデータヘッダーのセットとしてメタデータを渡すことで、アプリケーションは一連の前提条件でリクエストをすばやく拒否できるようになります。この仕組みは、必要に応じてアプリケーション層より上(リバースプロキシやCDNなど)でも適用できます。
1.1. 例
picture
要素から生成されるリクエストは、以下のHTTPリクエストヘッダーを含みます:
Sec-Fetch-Dest: image Sec-Fetch-Mode: no-cors Sec-Fetch-Site: cross-site
ユーザーがページ内リンクをクリックし、https://example.com から https://example.com/
へのトップレベルナビゲーションを行った場合、以下のHTTPリクエストヘッダーが含まれます:
Sec-Fetch-Dest: document Sec-Fetch-Mode: navigate Sec-Fetch-Site: same-origin Sec-Fetch-User: ?1
2. フェッチメタデータヘッダー
以下のセクションでは、いくつかのフェッチメタデータヘッダーを定義します。これらはそれぞれ、リクエストの属性をサーバーに公開します。
2.1. Sec-Fetch-Dest HTTPリクエストヘッダー
Sec-Fetch-Dest HTTPリクエストヘッダーは、リクエストのdestinationをサーバーに公開します。これは値がトークンでなければならないStructured Fieldです。[RFC9651]
ABNFは次のとおりです:
Sec-Fetch-Dest = sf-token
有効なSec-Fetch-Dest値は、リクエストに対して
destinationとして
[Fetch]で定義されているものの集合です。
まだ知られていないリクエストタイプとの前方互換性を持たせるため、このヘッダーが無効な値を持つ場合はサーバーは無視するべきです。
//fetch()のdestinationは空文字列 Sec-Fetch-Dest: empty //<img>のdestinationは"image" Sec-Fetch-Dest: image //new Worker()のdestinationは"worker" Sec-Fetch-Dest: worker // トップレベルナビゲーションのdestinationは"document" Sec-Fetch-Dest: document //<iframe>ナビゲーションのdestinationは"iframe" Sec-Fetch-Dest: iframe
Sec-Fetch-Dest ヘッダーを設定するには:
-
表明: r の現在の URLは、信頼できる可能性のある URLである。
-
r の宛先が空文字列である場合、 header の値を文字列 "
empty" に設定する。それ以外の場合、header の値を r の宛先に設定する。 -
r のヘッダーリストで、 `
Sec-Fetch-Dest`/header の構造化フィールド値を設定する。
2.2. Sec-Fetch-Mode HTTPリクエストヘッダー
Sec-Fetch-Mode HTTP 要求ヘッダーは、要求の
モードをサーバーに公開する。これは、値が
構造化フィールドであり、その値は
トークンでなければならない。 [RFC9651] その ABNF は次のとおりである:
Sec-Fetch-Mode = sf-token
有効な Sec-Fetch-Mode の値には、"cors"、"navigate"、
"no-cors"、"same-origin"、
および "websocket" が含まれる。まだ未知の要求タイプとの前方互換性をサポートするため、
無効な値が含まれている場合、サーバーはこのヘッダーを無視すべきである。
Sec-Fetch-Mode ヘッダーを設定するには:
-
表明: r の現在の URLは、信頼できる可能性のある URLである。
-
header の値を r のモードに設定する。
-
r のヘッダーリストで、 `
Sec-Fetch-Mode`/header の構造化フィールド値を設定する。
2.3. Sec-Fetch-Site HTTPリクエストヘッダー
Sec-Fetch-Site HTTP 要求ヘッダーは、要求の起動元のオリジンとその対象のオリジンとの関係を公開する。これは、値が構造化フィールド
であり、その値はトークンである。 [RFC9651] その ABNF は次のとおりである:
Sec-Fetch-Site = sf-token
有効な Sec-Fetch-Site の値には、"cross-site"、"same-origin"、
"same-site"、および "none" が含まれる。
まだ未知の要求タイプとの前方互換性をサポートするため、無効な値が含まれている場合、サーバーは
このヘッダーを無視すべきである。
Sec-Fetch-Site ヘッダーを設定するには:
-
表明: r の現在の URLは、信頼できる可能性のある URLである。
-
header の値を
same-originに設定する。 -
r が、ユーザーによる ユーザーエージェントとの操作(たとえば、ユーザーエージェントにアドレスを直接入力すること、または ブックマークをクリックすることなど)によって明示的に発生したナビゲーション要求である場合、header の値を
noneに設定する。注: このやや不明確に定義された手順の詳細については、§ 4.3 ユーザーによって直接開始された要求を参照。
-
header の値が
noneでない場合、r のURL リスト内の各 url について: -
r のヘッダーリストで、 `
Sec-Fetch-Site`/header の構造化フィールド値を設定する。
2.4. Sec-Fetch-User HTTPリクエストヘッダー
Sec-Fetch-User HTTP 要求ヘッダーは、
ナビゲーション要求がユーザーアクティベーションによってトリガーされたかどうかを公開する。これは、値が構造化フィールドであり、その
値はブール値である。 [RFC9651] その ABNF は次のとおりである:
Sec-Fetch-User = sf-boolean
注: このヘッダーはナビゲーション要求に対してのみ、かつその値が true の場合にのみ配信される。
将来的には、その情報を公開することで改善されるユースケースを明確に示すことができ
(また、関心のあるすべてのサブリソース要求タイプについてその状態を定義する方法に合意できる)のであれば、
ヘッダーの適用範囲をサブリソース要求全般に拡張することも合理的かもしれないが、現時点では、
ナビゲーション要求には明確なユースケースがあり、相互運用可能な形で定義することも
容易であるように思われる。
Sec-Fetch-User ヘッダーを設定するには:
-
表明: r の現在の URLは、信頼できる可能性のある URLである。
-
r がナビゲーション要求でない場合、または r のユーザーアクティベーションが
falseである場合、返る。 -
header の値を
trueに設定する。 -
r のヘッダーリストで、 `
Sec-Fetch-User`/header の構造化フィールド値を設定する。
3. FetchおよびHTMLとの統合
Sec-Fetch-User をサポートするため、要求は、HTML のフェッチによってナビゲーションパラメーターを作成するアルゴリズムによって別途設定されない限り、false であるユーザーアクティベーションを持つ。
Fetch メタデータヘッダーは、次の手順を使用して、Fetch の「HTTP-network-or-cache」 アルゴリズム内から送信される要求に追加される。統合の詳細については、その仕様を参照すること [FETCH]。
-
r の現在の URLが信頼できる可能性のある URLでない場合、返る。
-
r に対して
Sec-Fetch-Destヘッダーを設定する。 -
r に対して
Sec-Fetch-Modeヘッダーを設定する。 -
r に対して
Sec-Fetch-Siteヘッダーを設定する。 -
r に対して
Sec-Fetch-Userヘッダーを設定する。
4. セキュリティおよびプライバシーの考慮事項
4.1. リダイレクト
ユーザーエージェントは、リダイレクトチェーン内の各
要求とともに、Sec-Fetch-Site ヘッダーを送信する。
混乱を軽減するため、クロスオリジンまたはクロスサイトの
リダイレクトが存在する場合、ヘッダーの値は変化する。
Sec-Fetch-Site ヘッダーを設定するアルゴリズムは、要求の
URL リスト全体を走査し、リスト内のいずれかの URL が
要求の現在の URLに対してクロスサイトであれば cross-site を送信し、リスト内のすべての URL が
要求の現在の URLと同一サイトである場合にのみ same-site を送信し、リスト内のすべての URL が
要求の現在の URLと同一オリジンである場合にのみ same-origin を送信する。
たとえば、https://example.com/ が https://example.com/redirect を要求した場合、
最初の
要求の Sec-Fetch-Site 値は same-origin となる。その応答が
https://subdomain.example.com/redirect にリダイレクトした場合、その要求の Sec-Fetch-Site 値は
same-site となる(https://subdomain.example.com/ と https://example.com/
は同じ
登録可能ドメインを持つため)。その応答が https://example.net/redirect にリダイレクトした場合、その
要求の Sec-Fetch-Site 値は cross-site となる(
https://example.net/ は
https://example.com/ および https://subdomain.example.com/ と同一サイトではないため)。その
応答が
https://example.com/ までリダイレクトして戻った場合でも、最終的な要求の
Sec-Fetch-Site
値は引き続き cross-site となる(リダイレクトチェーンには
https://example.net/ が含まれており、これは
依然として他の URL と同一サイトではないため。
注:
Sec-Fetch-Site: None という特殊な場合については、短縮リンクをアドレス
バーにコピー&ペーストする一般的なケースをサポートするため、リダイレクトを通じてその値を維持することが合理的と思われる。
つまり、ユーザーエージェントが https://sho.rt/link へのアドレスバーからのナビゲーションを
Sec-Fetch-Site: none として扱うことを選択した場合、リダイレクト後の
https://target.com/long/path/goes/here へのナビゲーションも
同様に Sec-Fetch-Site: none を表明すべきである。
4.2. Sec-プレフィックス
この文書で定義される各ヘッダーには Sec- という接頭辞が付けられており、そのすべてが
禁止応答ヘッダー名となるため、
JavaScript から変更することはできない。これにより、
悪意のあるウェブサイトがユーザーエージェントを誘導して、偽造されたメタデータを要求とともに送信させることを防止でき、
サイトは通知された情報に対して適切に応答できる能力について、
より高い信頼を持てるようになるはずである。
4.3. ユーザーによる直接リクエスト
Sec-Fetch-Site ヘッダーを設定する際、ユーザーエージェントには、
「ユーザーの操作によって明示的に引き起こされた」ナビゲーション要求を
区別することが求められる。このやや不明確に定義された表現は HTML から引用されたものであり、
そこでは
次のように示唆されている:
「ユーザーエージェントは、この仕様で定義されているものに加えて、ユーザーが明示的に閲覧コンテキストを
ナビゲートさせるためのさまざまな方法を提供してもよい。」
目的は、特定の(悪意がある可能性もある!)ウェブサイトによって制御される「ウェブ的な」
ナビゲーション(たとえばリンク、window.location セッター、フォーム送信など)と、
そうでないもの(たとえばユーザーエージェントのアドレスバー、ブックマークなどに対するユーザー操作)を区別することである。前者には、
必要に応じて値が same-origin、
same-site、または
cross-site である Sec-Fetch-Site ヘッダーが付けられて送信される。後者は none という値によって区別される。
実際には特定のサイトがその要求に責任を負っているわけではなく、何らかの形でユーザーの直接的な意図を表しているため、
サーバーがそれらを信頼されたものとして扱うことを許可するのは理にかなっている。
各ユーザーエージェントには、一方または他方の カテゴリーに該当し得る独自の操作の集合がある可能性が高く、これらのケースについて自動テストスイートを共有することは難しい。それでも、 一般的である可能性が高いものについては、動作を一致させることが理想的である。以下にいくつか例を示す:
-
アドレスバーからのナビゲーション: 一般的な場合、この種のナビゲーションは ユーザーによって直接開始されたものとして扱い、
Sec-Fetch-Site: noneを含めるべきである。ユーザーエージェントが、 アドレスバーへの値の貼り付けに関するヒューリスティックを含めること(特に 「コピー」操作を特定のオリジンまで追跡できる場合)や、そのようなナビゲーションを ユーザー自身が入力したものとは区別して扱うことは合理的かもしれない。 -
ユーザーエージェント UI(ブックマーク、新しいタブページなど)からのナビゲーション: ユーザーエージェント UI 内のリンクに対する ユーザーの操作は、ユーザーエージェントのアドレスバーへの入力と同様に扱い、 ナビゲーションがユーザーによって開始されたことを示すために
Sec-Fetch-Site: noneを含めるべきである。 -
リンクのコンテキストメニュー(たとえば「新しいウィンドウで開く」)からのナビゲーション: リンクの対象は そのリンクが存在するページによって制御されるため、ユーザーエージェントはそのナビゲーションを サイトによって制御されたものとして扱い、リンクを制御する サイトと開かれるサイトとの関係に応じて
Sec-Fetch-Siteヘッダーを適切に設定すべきである。 -
リンクを Ctrl キーを押しながらクリック: ここでも、直前で説明したリンクのコンテキスト メニューの場合と同じ議論および結論が適用される。
-
履歴を介したナビゲーション(たとえばユーザーエージェントの「戻る」ボタン):
-
ドラッグ&ドロップ: ここでは、ドラッグされたコンテンツの出所に基づいて 動作を区別することが合理的と思われる。コンテンツがタブからドラッグされた場合、ユーザーエージェントは そのオリジンを特定し、それに応じて
Sec-Fetch-Siteを設定できるはずである。コンテンツが他の場所 (ユーザーエージェントのブックマークバー、まったく別のアプリなど)からドラッグされた場合は、Sec-Fetch-Site: noneが 適切である可能性がある。
4.4. 拡張機能によるリクエスト
一部のユーザーエージェントは、要求を行うことができ、ユーザーがウェブ体験をより細かく制御できるようにするため、 通常のウェブコンテンツを超える権限を与えられる拡張機能をサポートしている。これらは ウェブプラットフォームの範囲からやや外れるものではあるが、ユーザーエージェントには、 これらの要求をサーバーに対してどのように表現するかを慎重に検討することが推奨される。一般に、ユーザーエージェントが 次の 2 つの目標を満たすことが理想的である:
-
特定のサイトに対する特別な権限を持たない拡張機能が、そのサイトの サーバー側の Fetch Metadata ロジックを回避する要求を発生させることができないようにする。
-
開発者が拡張機能からの要求を認識できるようにし、必要に応じてサーバーの Fetch Metadata ロジックの適用対象から除外できるようにする。これにより、正当なユーザーの利益を損なうことなく、 開発者が Fetch Metadata による保護を確信を持って導入できる能力が高まる。
これらの目標を念頭に置き、ユーザーエージェントには次の動作を実装することが推奨される:
-
拡張機能が特定の URL にアクセスする権限を持っていない場合、その URL への要求には、 通常のウェブ要求の場合と同様に、値が
cross-siteであるSec-Fetch-Siteヘッダーを含めることができる。 拡張機能が特定の URL へのアクセス権を持っている場合、Sec-Fetch-Siteの値はsame-originとすることができる。 -
拡張機能コンテキストから送信される要求には
Originヘッダーを含めることができ、その値には、 サーバーが拡張機能によって開始された要求と ウェブによって開始された要求を区別できる実装定義の値を使用できる。
5. 導入上の注意点
5.1. Vary(可変)
特定のエンドポイントの応答が、クライアントが
Fetch メタデータ
ヘッダーで送信する値に依存する場合、開発者は適切な Vary ヘッダーを含めるよう注意すべきである
[RFC9110]。これにより、
キャッシュが応答を適切に処理できるようになる。たとえば、
Vary: Accept-Encoding, Sec-Fetch-Site。
5.2. ヘッダー肥大化
この文書の以前のバージョンでは、内容が辞書である単一の Sec-Metadata ヘッダーを定義していた。
その後の議論(および Mark Nottingham による優れた
[mnot-designing-headers])を受けて、設計は単一の辞書から
一連の単純なヘッダーへと変更され、それぞれのヘッダーには単一のトークンのみが含まれるようになった。この設計は、
HTTP の現在の HPACK 圧縮アルゴリズムの下で大幅に優れた性能を発揮するはずである。
このトピックに関するさらなる議論は、 w3ctag/design-reviews#280 のレビュースレッドで確認できる。
6. IANA 関連事項
恒久的メッセージヘッダーフィールドレジストリは、 Fetch メタデータ ヘッダーについて、次の登録を追加するよう更新されるべきである: [RFC3864]
6.1. Sec-Fetch-Dest 登録
- ヘッダーフィールド名
-
Sec-Fetch-Dest
- 該当プロトコル
-
http
- ステータス
-
standard
- 著者/変更管理者
-
Me
- 仕様文書
6.2. Sec-Fetch-Mode 登録
- ヘッダーフィールド名
-
Sec-Fetch-Mode
- 該当プロトコル
-
http
- ステータス
-
standard
- 著者/変更管理者
-
Me
- 仕様文書
6.3. Sec-Fetch-Site 登録
- ヘッダーフィールド名
-
Sec-Fetch-Site
- 該当プロトコル
-
http
- ステータス
-
standard
- 著者/変更管理者
-
Me
- 仕様文書
6.4. Sec-Fetch-User 登録
- ヘッダーフィールド名
-
Sec-Fetch-User
- 該当プロトコル
-
http
- ステータス
-
standard
- 著者/変更管理者
-
Me
- 仕様文書
7. 謝辞
この仕組みの設計にあたり、多大なご助力をいただいたAnne van Kesteren、Artur Janc、Dan Veditz、Łukasz Anforowicz、Mark Nottingham、Roberto Clapis各氏に感謝します。