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に対して):
-
headerを、値がトークンであるStructured Fieldとして初期化する。
-
もしrのdestinationが空文字列であれば、 headerの値を文字列 "
empty"とする。そうでなければ、headerの値をrのdestinationとする。注: Fetchの空文字列destinationは、処理の簡便化のため明示的な "
empty" トークンにマップされます。 -
構造化フィールド値をセットする: `
Sec-Fetch-Dest`/header をrのヘッダーリストに。
2.2. Sec-Fetch-Mode HTTPリクエストヘッダー
Sec-Fetch-Mode HTTPリクエストヘッダーは、リクエストのmodeを
サーバーに公開します。これは値がトークンでなければならないStructured Fieldです。[RFC9651]
ABNFは次のとおりです:
Sec-Fetch-Mode = sf-token
有効なSec-Fetch-Mode値には、"cors"、"navigate"、
"no-cors"、"same-origin"、
"websocket"が含まれます。知られていないリクエストタイプとの前方互換性のため、このヘッダーが無効な値の場合はサーバーは無視するべきです。
Sec-Fetch-Modeヘッダーをセットするための手順(リクエスト
rに対して):
-
header を値がトークンであるStructured Fieldとして初期化する。
-
headerの値を r のmodeに設定する。
-
構造化フィールド値をセットする: `
Sec-Fetch-Mode`/header をrのヘッダーリストに。
2.3. Sec-Fetch-Site HTTPリクエストヘッダー
Sec-Fetch-Site HTTPリクエストヘッダーは、リクエストのイニシエータのオリジンとターゲットのオリジンの関係を公開します。これはStructured Fieldで、その値はトークンです。
[RFC9651]
ABNFは次のとおりです:
Sec-Fetch-Site = sf-token
有効なSec-Fetch-Site値には、"cross-site"、"same-origin"、
"same-site"、"none" が含まれます。
前方互換性のため、無効な値の場合はサーバーはこのヘッダーを無視するべきです。
Sec-Fetch-Siteヘッダーをセットするための手順(リクエスト
rに対して):
-
header を値がトークンであるStructured Fieldとして初期化する。
-
headerの値を
same-originに設定する。 -
もしrがユーザーの明示的な操作によるナビゲーションリクエストである場合 (たとえば、アドレスバーへの直接入力やブックマークのクリック等)、headerの値を
noneに変更する。注: このやや不明確なステップの詳細は § 4.3 ユーザーによる直接リクエストを参照してください。
-
headerの値が
noneでない場合は、 rのurl list内の各urlについて: -
構造化フィールド値をセットする: `
Sec-Fetch-Site`/header をrのヘッダーリストに。
2.4. Sec-Fetch-User HTTPリクエストヘッダー
Sec-Fetch-User HTTPリクエストヘッダーは、ナビゲーションリクエストがユーザー操作で発生したかどうかを公開します。これはStructured Fieldで、その値はbooleanです。[RFC9651]
ABNFは次のとおりです:
Sec-Fetch-User = sf-boolean
注: このヘッダーはナビゲーションリクエスト かつ値がtrueの場合のみ送信されます。
一般的なサブリソースリクエストについても将来的に拡張するのは合理的かもしれませんが、その情報公開によって利便性向上が見込まれるユースケースがあり、かつ全サブリソースリクエストタイプの状態定義方法に合意できれば、検討されるでしょう。当面は、ナビゲーションリクエストの明確なユースケースが存在し、相互運用性の観点で定義しやすいです。
Sec-Fetch-Userヘッダーをセットするための手順(リクエスト
rに対して):
-
もしrが ナビゲーションリクエスト でない、 または rのuser-activationが
falseの場合は、処理を戻す。 -
header をStructured Field(値はトークン)として初期化する。
-
headerの値を
trueとする。 -
構造化フィールド値をセットする: `
Sec-Fetch-User`/header をrのヘッダーリストに。
3. FetchおよびHTMLとの統合
Sec-Fetch-Userをサポートするために、requestはuser-activationを持ち、これはHTMLのcreate navigation params by
fetchingアルゴリズムにより設定されない限り、false となります。
フェッチメタデータヘッダーは、Fetchの "HTTP-network-or-cache" アルゴリズム内から発信されるリクエストに付加されます。詳細な統合については該当仕様書を参照してください [FETCH]。
4. セキュリティおよびプライバシーの考慮事項
4.1. リダイレクト
ユーザーエージェントは、リダイレクトチェーンの各リクエストにSec-Fetch-Siteヘッダーを送信します。クロスオリジンまたはクロスサイトリダイレクトが発生した場合、混乱を避けるためにヘッダーの値が変化します。
Sec-Fetch-Siteヘッダーを設定するアルゴリズムは、request のurl list全体を走査し、リスト内にリクエストのcurrent
urlとクロスサイトなURLがあればcross-site、全てが同じサイトならsame-site、全てが同一オリジンなら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://example.net/が含まれているため、最終リクエストもcross-siteのままです。
注: 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から引用されており、HTMLでは「ユーザーエージェントは、この仕様で定義されているものに加え、さまざまな方法でユーザー操作により閲覧コンテキストをナビゲートさせることができる」と示唆されています。
目標は、「ウェブ側で制御されたナビゲーション」(例: リンク、window.locationの設定、フォーム送信等)と、そうでないもの(例:
アドレスバーやブックマーク等のユーザーエージェントUI操作)を区別することです。前者にはSec-Fetch-Siteヘッダーに適切な値(same-origin、same-site、cross-site)を付与し、後者はnoneとします。これは、特定のサイトが実際にリクエストを引き起こしていないことを示し、サーバー側で信頼できる操作として扱うことに意味があります。
各ユーザーエージェントには、それぞれ独自の分類基準があるかもしれません。またこれらケースに対する自動試験スイートを共有するのは難しいでしょう。それでも一般化しやすい動作については足並みを揃えるのが理想です。以下にいくつかの例を示します:
-
アドレスバーからのナビゲーション:原則としてこのようなナビゲーションはユーザーによる直接操作とみなし、
Sec-Fetch-Site: noneを付与すべきです。ユーザーエージェントによっては、アドレスバーへのペースト(特に「コピー」元のオリジンが特定できる場合)を区別して扱うヒューリスティクスを持たせてもよいでしょう。 -
ユーザーエージェントUI(ブックマーク、新しいタブページ等)からのナビゲーション:これもアドレスバー入力と同様に
Sec-Fetch-Site: noneをつけてユーザー操作であると示すべきです。 -
リンクのコンテキストメニュー(「新しいウィンドウで開く」等)によるナビゲーション:この場合はリンク先がページの制御下にあり、ユーザーエージェントはサイト制御とみなして対応する
Sec-Fetch-Site値をセットするべきです。 -
Ctrl+クリックによるリンク:上記のコンテキストメニューと同様に扱います。
-
履歴を使ったナビゲーション(戻るボタン等):
-
ドラッグ&ドロップ:ドラッグ元に応じた動作を区別するのが合理的です。タブからのドラッグならオリジンを判断し
Sec-Fetch-Site値を設定すべきですが、ブックマークバーや他アプリ由来ならSec-Fetch-Site: noneが妥当です。
4.4. 拡張機能によるリクエスト
一部のユーザーエージェントは、拡張機能が通常のWebコンテンツよりもユーザーのWeb体験をコントロールできるように特権的なリクエスト送信をサポートしています。これらはWebプラットフォームの範囲外ですが、ユーザーエージェントにはそのようなリクエストをサーバーにどう表現するかを慎重に検討することを推奨します。一般に、以下2つの目標を満たすのが理想です:
-
特別な権限がない拡張機能は、そのサイトのFetch Metadataロジックを迂回できるリクエストを送出できないようにする。
-
開発者が拡張機能によるリクエストを識別し、サーバー側のFetch Metadataロジックから除外するかどうかを制御できるようにする。これにより、Fetch Metadata保護を自信を持って導入しつつ正当なユーザー利便を損なうリスクを低減できます。
これらの目標のもと、ユーザーエージェントは以下の動作を実装することが推奨されます:
-
拡張機能が特定URLへのアクセス権を持たない場合、そのリクエストには通常のWebリクエスト同様
Sec-Fetch-Site: cross-siteを付与することができます。権限がある場合はSec-Fetch-Site: same-originとすることができます。 -
拡張機能からの送信リクエストには
Originヘッダーを含め、その値は実装依存としサーバーがWeb由来か拡張由来か判別できるようにします。
5. 導入上の注意点
5.1. Vary(可変)
特定のエンドポイント応答がクライアントから渡されるFetchメタデータヘッダーの値に依存する場合、開発者はキャッシュが正しく処理できるよう適切な
Vary ヘッダーを含めるよう注意してください [RFC9110]。例: Vary: Accept-Encoding, Sec-Fetch-Site。
5.2. ヘッダー肥大化
以前のバージョンでは、内容がディクショナリ型の Sec-Metadata ヘッダー1つにまとめていました。その後の議論(および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各氏に感謝します。