Post-Spectre Web Developmentを読む
W3CのPost-Spectre Web Developmentを読んで自分なりに解釈したものを書いていきます。
解釈したものが間違っていたらコメント等もらえると嬉しいです。
概要
このドキュメントは自分のアプリケーションのデータ等が攻撃者のプロセスに入らないようにするための方法を解説しているドキュメント
ここから元のドキュメントのIntroductionの章
ブラウザの内部構造の変異
SPECTRE攻撃によって、今までのブラウザのデータの分け方だと他のスクリプト(e.g. google analyticsのスクリプトみたいなやつ)とかからデータを読み取ることができる可能性があってからそれがセキュリティ的に危ないことがわかった!→OSのプロセス機能でオリジンごとにアプリケーションのデータを分けるようにして安全性をあげよう!
といった形でブラウザのデータの分け方がOSのプロセス機能を基準としたものに変わった
攻撃者が読み取ることができるデータの範囲を想像できるようになるためブラウザ内部のプロセスの実装についてどのようになっているか知る
- ブラウザ内部でブラウザコア部分のプロセスはアプリケーションを動かしているプロセスとは独立している。→それにより「ローカルデバイスへのアクセス」や「リソースの取得」、「プロセス間の通信の仲介」などの重要な処理をウェブアプリケーションを動かしているプロセスに隠して実行できる
- ブラウザは取得したリソース(e.g. abc.com/sample.img)をリクエストとレスポンスのヘッダーなどを見ながら特定のオリジン(e.g. abc.com)を動かしているプロセスに渡すかどうかを判断します
- ブラウザはクロスオリジンのリソースのWindowを別のプロセスに分けます。(e.g. abc.comとdef.com)、しかしsame-siteやsame-originのリソースは同じプロセスに渡されます
- iframeやembedとかの埋め込みコンテンツをその埋め込み元のオリジンのプロセスから分離することはまだできていない(ただAndroid Chrome等ではパスワードを入力したりするサイトに関して別プロセスに分離する処理を加えたりしているよう[参考], Site Isolationで調べたらもっと理解が深まるかも)
この4つを踏まえて、プロセスの分離について二つの認識を持つ必要がある
- オリジンはそのプロセスがレンダリングする全てのリソース(画像、css,スクリプト、フレーム(iframeとか))へのアクセスをもつということ、つまり同一オリジンのデータについては全てアクセスできる。
- 埋め込みコンテンツ(iframeやembed)はそれを埋め込んでいるアプリケーションのデータへSPECTRE脆弱性をついてアクセスができる
理解があやふやだった用語調べた
Window: タブにつき一つ作られるオブジェクト、DOMや関数、オブジェクトなどとくかくそのサイトに関するものがたくさん入ってる(参考:https://developer.mozilla.org/ja/docs/Web/API/Window)
same-origin: scheme(http | https)とホストネーム(www.example.com)とポート(80番ポート)が同じもの(https://example.comとhttps://www.example.comはcross-originになる)
same-site: eTLD(.comや.github.io)とその一つ前の単語(example.comのexample)が同じもの(eTLDはPublic suffix listでデータベース化されている)(参考:https://web.dev/same-site-same-origin/#:~:text=.com%3A443 .-,"same-origin" and "cross-origin",considered "cross-origin".)
ここからPractical Exampleの章
2.1 サブリソースについて
サブリソース(画像とかスクリプトとか)は予期しない方法で使用されないように保護する必要がある→そのために必要な戦略がリソースの種類ごとにあるのでそれをこれから説明します。
ただそれを説明する前にその戦略を実行するのに必要ないくつかのヘッダーがあるのでそれを最初に説明する。
- サーバーはFetch MetaDataヘッダー(Sec-Fetch-Site, Sec-Fetch-Dest, Sec-Fetch-Mode, Sec-Fetch-User)を使用してそれぞれのリクエスト元などに応じて処理を変えることができます。Fetch MetaDataヘッダーはブラウザが自動で追加するのでアプリケーション開発者側から変更することができなくてサーバーでこのヘッダーの内容を信用することができます。ただし、Fetch MetaDataヘッダーの違いによってサーバー側でレスポンスを変えることが想定されるため、レスポンスのVaryフィールドにSec-Fetch-Site等を指定しておいてCDNを使ったレスポンスに不整合が起こさないようにする必要があります。
加えてまだ全ブラウザで対応されているわけではないっぽい- Sec-Fetch-Site: cross-site, same-origin, same-site, none
- Sec-Fetch-Mode: cors, navigate, nested-navigate, no-cors, same-origin, websocket
※リクエストのモード、CORSやno-corsなど設定されてる。(ここが参考になりそう) - Sec-Fetch-User: ナビゲーションリクエストがユーザーの操作によって引き起こされたものか否か(boolean)
- Sec-Fetch-Dest: 取得されたコンテンツがどのような用途で使われるか判定する(audio,font,script,styleなどの値がある)
- X-Content-Type-Optionsヘッダーをnosniffにサーバーで設定することでMIME type sniffing(MIMEタイプが欠落していたりする場合にブラウザがリソースからMIMEタイプを推測して設定する挙動のこと)を防ぎ、CORBやORBのようなMIMEベースのチェック機能の堅牢性を強化する。
- サブリソースはナビゲーション(それ単体でブラウザのタブを一個開かれるやつ、普通はHTMLファイルが適用対象)として呼び出されることを想定しておらず、HTMLなどからその構成要素として呼び出されることを想定している。
そのことから、ナビゲーションで呼び出されて攻撃されるリスクを抑えるために以下のヘッダーをサーバー側で追加することを推奨する- Content-Security-Policy(CSP)ヘッダーにsandboxという値を設定することでナビゲーションでサブリソースをリクエストされた際にブラウザのsandbox機能が有効になりスクリプトの実行などがされなくなる。
- Cross-Origin-Opener-Policy(COOP)ヘッダーにsame-originの値を設定することでクロスオリジンのサイトをaタグ等を踏んで開いた際にwindow.openerで元の開いたサイトの情報を取得できてしまうという問題を解決する。例えばサイトA(abc.com)からaタグを踏んでサイトB(def.com)を開いた際にサイトBがwindow.openerでサイトAの情報を取れてしまう問題があるが、COOPヘッダーをsame-originに設定しておくと取られない。これに関してこの記事でわかりやすくまとめてくれていた(ちなみにaタグのrel=noopenerでも同じ効果があるけど、このヘッダーをつけとくとchromeなど対応しているブラウザではつけ忘れた時とか安心)
- X-Frame-OptionsヘッダーをDENYにしてiframe等でフレームとして読み込まれるのを防ぎましょう!
ここで説明したこと全部やるとレスポンスヘッダーはこんな感じになる
Content-Security-Policy: sandbox // sandbox機能が有効になってスクリプトの実行等がされなくなる
Cross-Origin-Opener-Policy: same-origin // same-originでない限り呼び出したサイトから呼び出し元のサイトの情報を取れなくなる
Vary: Sec-Fetch-Dest, Sec-Fetch-Mode, Sec-Fetch-Site // CDNでFetch Metadataによってレスポンスを場合分けする用
X-Content-Type-Options: nosniff // ブラウザのMIME推測を無効にしてCORBやORBなどのブラウザのセキュリティ機能を堅牢にするため
X-Frame-Options: DENY // iframe等でフレームとして呼ばれないようにするため
理解があやふやだった用語調べた
- CORS: 異なるオリジンのリソースへのアクセス権を与えるように支持するための仕組み(参考: https://developer.mozilla.org/ja/docs/Web/HTTP/CORS)
- no-cors: fetch APIなどで設定されるモードの一つ。モードとしてはcors, same-origin, no-corsがあり、corsはクロスオリジンリソース共有を実行し、same-originは同一オリジン以外のアクセスをエラーにする、no-corsはクロスオリジンリソース共有ができない場合エラーにならず空のレスポンスが返される(参考: https://qiita.com/att55/items/2154a8aad8bf1409db2b#fetch-api-の-mode-について)
- MIMEタイプ: 文書やファイルの性質や形式を示す標準(e.g. application/pdfやfont/woffなどがある)(参考: https://developer.mozilla.org/ja/docs/Web/HTTP/Basics_of_HTTP/MIME_types)
- CORB(Cross-Origin Read Blocking): 怪しいクロスオリジンのリソース(scriptとか)をウェブアプリケーションで読み込む前にブラウザがブロックすることができるアルゴリズム、機密データをクロスオリジンのWebページから遠ざけたりする。(参考: https://www.chromestatus.com/feature/5629709824032768#:~:text=Cross-Origin Read Blocking (CORB) is an algorithm that,from cross-origin web pages.)
- ORB(Opaque Response Blocking): 不透明なMIMEタイプ(e.g. HTML MIMEタイプやJSON MIMEタイプなど)のリソースをブロックするという機能(参考: https://github.com/annevk/orb)
- Varyヘッダー: 最近はCDNなどが発展していてキャッシュがあるURLはアプリケーションサーバーに届く前にCDNで返すことが多々ある。けれど同じURLでもAccept-Languageなどのヘッダの違いによってアプリケーションサーバーが返すコンテンツが違う場合がある。そういった状況に対応するためVaryヘッダーにAccept-Languageなどサーバーがその値によって返すコンテンツを変えるヘッダーを指定しておくことでCDNにキャッシュを使うか使わないかの場合分けを指示する。(参考: https://rtam.xyz/articles/2018/02/about-http-vary-field/)
- sandbox機能: irameやContent-Security-Policyなどで指定されるディレクティブでそのリソースに対して「scriptの実行を許可するか」、「ポップアップを許可するか」といったような制限を加えることができる(参考: https://developer.mozilla.org/ja/docs/Web/HTTP/Headers/Content-Security-Policy/sandbox)
2.1.1 静的なサブリソースについて
静的なサブリソース(画像とか)は誰が読み込んでも同じものが返されるのでリソースを比較的簡単に取得できるようにしてしまって大丈夫。開発者がスムーズに開発できるようにすることの方が大事。そのため下のようなヘッダーを持たせると良さそう。
Access-Control-Allow-Origin: *
Cross-Origin-Resource-Policy: cross-origin
Timing-Allow-Origin: *
Content-Security-Policy: sandbox
Cross-Origin-Opener-Policy: same-origin
X-Content-Type-Options: nosniff
X-Frame-Options: DENY
知らなかったヘッダー調べた
- Timing-Allow-Origin: Resource Timing APIというリソースの取得にどれくらいかかったか計測することができるブラウザのAPIがある。そのAPIを使って計測するのを許可するオリジンを指定するためのヘッダー(https://example.com みたいなのを指定する)
すごいわかりやすく解説されている記事を知ってしまった。
動的なサブリソースについて
ユーザーデータなどの動的なリソースは攻撃者にとって格好の標的になる。その為それぞれのデータごとに適切な読み込み方法を設定する必要がある。
-
基本、プライベートなAPIやアバター画像、アップロードされたデータなどのアプリケーション内部のデータはcross-originから一切利用できないようにすべき。そのためにCross-Origin-Resource-Policyヘッダーにsame-originを設定してレスポンスを返すことでそのAPIやデータを使えるのをsame-originのサイトのみに制限する。これによってクロスオリジンの攻撃者がno-corsでリクエストした際にレスポンスを返してしまうことを防ぐ。
Cross-Origin-Resource-Policy: same-origin Content-Security-Policy: sandbox Cross-Origin-Opener-Policy: same-origin Vary: Sec-Fetch-Dest, Sec-Fetch-Mode, Sec-Fetch-Site X-Content-Type-Options: nosniff X-Frame-Options: DENY
-
クロスオリジンでも使えるパーソナルデータなどを含んだAPIを作成する時はレスポンスを返す前にリクエストのヘッダーの内容を精査する必要がある。またこういったAPIを安全に使用できるようにするためにはCORSや適切なアクセスコントロールヘッダを設定し、このAPIからデータを受け取ることができるオリジンを限定する必要がある。
Access-Control-Allow-Credentials: true // Requestのcredentialsプロパティがincludeの時(つまりクロスオリジンの呼び出しであっても常にCookieを送信する時)、Access-Control-Allow-Credentialsがtrueでないとブラウザでデータを受け取ることができない Access-Control-Allow-Origin: https://trusted.example // データへのアクセスを許可するオリジンを指定する Access-Control-Allow-Methods: POST // プリフライトリクエストの際に次の本番リクエストで使えるメソッドを返す Access-Control-Allow-Headers: ... // プリフライトリクエストの際に本番リクエストで使えるHTTPヘッダーを返す Access-Control-Allow-...: ... Cross-Origin-Resource-Policy: same-origin Content-Security-Policy: sandbox Cross-Origin-Opener-Policy: same-origin Vary: Sec-Fetch-Dest, Sec-Fetch-Mode, Sec-Fetch-Site X-Content-Type-Options: nosniff X-Frame-Options: DENY
※Cross-Origin-Resource-Policyがsame-originになっていてクロスオリジンのサイトからアクセスできないような気がしてしまうが実際のところこのヘッダーはno-corsのクロスオリジンリクエストを防ぐだけでちゃんとCORSでリクエストすればAPIを叩くことができる
-
クロスオリジンでアバター画像などをimgタグ等のCORSを使えない状況で読み込ませたい時Cross-Origin-Resource-Policyをcross-originにする必要がある。これによりどのクロスオリジンのサイトでも読み込むことができてしまうが現状しょうがない。CORSでどうにかなりそうな物に関してはできるだけそっちを使おう。
Cross-Origin-Resource-Policy: cross-origin Content-Security-Policy: sandbox Cross-Origin-Opener-Policy: same-origin Vary: Sec-Fetch-Dest, Sec-Fetch-Mode, Sec-Fetch-Site X-Content-Type-Options: nosniff X-Frame-Options: DENY
- フルに隔離されたドキュメント
サインインを必要とするページ(アカウント管理ページ等)はほぼ確実に攻撃者に知られてはいけない情報があります。
こういったページはResource Isolation Policyのページを参考にリクエストに対して本当にページを配信するかといった部分からちゃんとサーバー側で判断するようにしてください。また配信する判断を下したとしても他のオリジンからアクセスできないようにするためクライアントに対してヘッダーを通して指示をする必要があります。以下のヘッダーを参考にしてください。
Cross-Origin-Opener-Policy: same-origin
Cross-Origin-Resource-Policy: same-origin
Vary: Sec-Fetch-Dest, Sec-Fetch-Mode, Sec-Fetch-Site
X-Content-Type-Options: nosniff
X-Frame-Options: SAMEORIGIN
- クロスオリジンのウィンドウを開くことを想定したドキュメント
サインインを必要とする全てのページがクロスオリジンのサイトから常に分離できるわけではないです。例えば「Signin with google」など他サイトとの連携を含むページはあります。このような場合では、通常制限しているクロスオリジンの埋め込みやwindowへのアクセスなどの能力を使いたいです。しかしCross-Origin-Opener-Policy: same-originやX-Frame-Options: DENYなどの制限を安易に解除するわけにはいきません。このような場合用に以下のレスポンスヘッダーをお勧めします。(※メモ:ここちゃんと原文の意味を意図通り解釈できたか怪しい...)
Cross-Origin-Opener-Policy: same-origin-allow-popups
Cross-Origin-Resource-Policy: same-origin
Vary: Sec-Fetch-Dest, Sec-Fetch-Mode, Sec-Fetch-Site
X-Content-Type-Options: nosniff
X-Frame-Options: SAMEORIGIN
このヘッダーの設定と「1.フルに隔離されたドキュメント」のヘッダーの違いはCOOPがsame-origin-allow-popupsになっている部分です。COOPがsame-originに設定されている場合は同じoriginでない限り開いたページに対してwindow.openerで現在のページへの参照を渡すことはありません。しかし、same-origin-allow-popupsに設定した際は同一オリジンのリソースが開いたクロスオリジンのページに対しては現在のページの参照を渡します。 もちろんクロスオリジンのリソースが開いたクロスオリジンのページに対しては現在のページへの参照は渡しません。
- クロスオリジンのサイトから開かれることを想定しているドキュメント
Stripeの決済やSignin with googleのgoogle側などはクロスオリジンのサイトからページを開かれることを想定しているサイトです。こういったサイトはpostMessage(message, option)やnavigationのような手段を用いて開いたサイトとコミュニケーションをする必要があるので開いた元のサイトとの関係を維持したいです。そのことから、このようなサイトは開いた元のサイトから完全に隔離することはありませんがクロスオリジンのサイトに埋め込まれたり、フェッチされたりするのを防ぐために色々と対策をすべきです。
今回は3つのシナリオとその対策を紹介します。
-
クロスオリジンのサイトでポップアップのみで開かれることを望むサイト
COOPをunsafe-noneにしてオープナーポリシーの規制を緩める
Cross-Origin-Resource-Policy: same-origin Cross-Origin-Opener-Policy: unsafe-none Vary: Sec-Fetch-Dest, Sec-Fetch-Mode, Sec-Fetch-Site X-Content-Type-Options: nosniff X-Frame-Options: SAMEORIGIN
-
クロスオリジンのサイトでFraming(iframeで読み込まれること等をさす)されることのみを望むサイト
X-Frame-OptionsをALLOWALLにしてフレーミングの規制を緩める
Cross-Origin-Resource-Policy: same-origin Cross-Origin-Opener-Policy: same-origin Vary: Sec-Fetch-Dest, Sec-Fetch-Mode, Sec-Fetch-Site X-Content-Type-Options: nosniff X-Frame-Options: ALLOWALL
ただこれに関しては本当に全てのサイトに対して埋め込みを許可してしまうのでできれば下のframe-ancestors CSPディレクティブを指定して埋め込みできるサイトを制限することをおすすめする
Content-Security-Policy: frame-ancestors https://trusted1.example https://trusted2.example
-
ポップアップとフレーミングの両方を許可したいサイト
COOP: unsafe-noneとX-Frame-Options: ALLOWALLを両方とも設定する
Cross-Origin-Resource-Policy: same-origin Cross-Origin-Opener-Policy: unsafe-none Vary: Sec-Fetch-Dest, Sec-Fetch-Mode, Sec-Fetch-Site X-Content-Type-Options: nosniff X-Frame-Options: ALLOWALL
知らない用語調べた
- window.postMessage(): iframeとそれを埋め込んだページや開かれた(window.open)ページと開いたページなどでクロスドメインの場合でも安全にメッセージを相互に通信することを可能にします。受け取る側はaddEventListener("message", func)みたいな形でメッセージを受け取ったりできる。(参考: https://developer.mozilla.org/ja/docs/Web/API/Window/postMessage)
実装上の注意点
今まで紹介してきたヘッダーの設定事項にはブラウザのデフォルト動作にも関わらず明示的に設定することを推奨しているものがあります。(COOPのunsafe-noneやX-Frame-Options: ALLOWALLなど)
これらの設定はデフォルト動作なのでいらないように思えるのですが、将来的にこういったデフォルトの設定値はより安全な設定に変えたいと考えており、そのことを考えると今から明示的に設定しておくことをおすすめします。
最後に場面別の対策をまとめる
対策を場面別にまとめる
サブリソース
-
静的なサブリソース
クロスオリジンからの読み込みができるなど開発者がスムーズに開発できるようにちょっと緩めに設定。
Access-Control-Allow-Origin: * Cross-Origin-Resource-Policy: cross-origin Timing-Allow-Origin: * Content-Security-Policy: sandbox Cross-Origin-Opener-Policy: same-origin X-Content-Type-Options: nosniff X-Frame-Options: DENY
-
自分のアプリケーション内でユーザーデータやアバター画像など動的なサブリソースを使う場合
最大限に厳しい規制をかける
Cross-Origin-Resource-Policy: same-origin Content-Security-Policy: sandbox Cross-Origin-Opener-Policy: same-origin Vary: Sec-Fetch-Dest, Sec-Fetch-Mode, Sec-Fetch-Site X-Content-Type-Options: nosniff X-Frame-Options: DENY
-
Google Calendar APIのようにクロスオリジンでパーソナルデータを含んだAPIを作る場合
この場合はCORSで通信をするのでAccess-Control-Allow-...系のヘッダーでちゃんと通信可能なオリジンをコントロールしよう
Access-Control-Allow-Credentials: true Access-Control-Allow-Origin: https://trusted.example Access-Control-Allow-Methods: POST Access-Control-Allow-Headers: ... Access-Control-Allow-...: ... Cross-Origin-Resource-Policy: same-origin Content-Security-Policy: sandbox Cross-Origin-Opener-Policy: same-origin Vary: Sec-Fetch-Dest, Sec-Fetch-Mode, Sec-Fetch-Site X-Content-Type-Options: nosniff X-Frame-Options: DENY
-
クロスオリジンのサイトでimgタグからの読み込み等CORSを使えない状況でも読み込ませたい場合
結構ゆるいけどしょうがない!!!
Cross-Origin-Resource-Policy: cross-origin Content-Security-Policy: sandbox Cross-Origin-Opener-Policy: same-origin Vary: Sec-Fetch-Dest, Sec-Fetch-Mode, Sec-Fetch-Site X-Content-Type-Options: nosniff X-Frame-Options: DENY
ドキュメント
-
ユーザー情報ページなどが必要で他のサイトからのアクセスを一切許したくない場合
下記の最大限規制が強いヘッダーとこのサイトを参考にサーバー側でドキュメントを返すか判断する
Cross-Origin-Opener-Policy: same-origin Cross-Origin-Resource-Policy: same-origin Vary: Sec-Fetch-Dest, Sec-Fetch-Mode, Sec-Fetch-Site X-Content-Type-Options: nosniff X-Frame-Options: SAMEORIGIN
-
federated Signin(Signin with google等)などでクロスオリジンのサイトと連携をしたい場合
same-originのリソースが開いたサイトのみwindow.openerなどでの連携を許可する
Cross-Origin-Opener-Policy: same-origin-allow-popups Cross-Origin-Resource-Policy: same-origin Vary: Sec-Fetch-Dest, Sec-Fetch-Mode, Sec-Fetch-Site X-Content-Type-Options: nosniff X-Frame-Options: SAMEORIGIN
-
federated Signinなどを提供する場合
-
ポップアップで開かれることを望む場合
Cross-Origin-Resource-Policy: same-origin Cross-Origin-Opener-Policy: unsafe-none Vary: Sec-Fetch-Dest, Sec-Fetch-Mode, Sec-Fetch-Site X-Content-Type-Options: nosniff X-Frame-Options: SAMEORIGIN
-
iframe等でフレーミングされることを望む場合
Cross-Origin-Resource-Policy: same-origin Cross-Origin-Opener-Policy: same-origin Vary: Sec-Fetch-Dest, Sec-Fetch-Mode, Sec-Fetch-Site X-Content-Type-Options: nosniff X-Frame-Options: ALLOWALL
Content-Security-Policy: frame-ancestors https://trusted1.example https://trusted2.example
-
ポップアップ、フレーミングどちらもできることを望む場合
Cross-Origin-Resource-Policy: same-origin Cross-Origin-Opener-Policy: unsafe-none Vary: Sec-Fetch-Dest, Sec-Fetch-Mode, Sec-Fetch-Site X-Content-Type-Options: nosniff X-Frame-Options: ALLOWALL
-