Fetch Metadata でウェブ攻撃からリソースを保護

CSRF、XSSI、クロスオリジン情報漏洩を防ぎます。

ウェブリソースを分離する必要がある理由

多くのウェブ アプリケーションは、クロスサイト リクエスト フォージェリ(CSRF)、クロスサイト スクリプト インクルージョン(XSSI)、タイミング攻撃、クロスオリジン情報漏洩、推測実行サイドチャネル(Spectre)攻撃などのクロスオリジン攻撃に対して脆弱です。

Fetch Metadata リクエスト ヘッダーを使用すると、強力な多層防御メカニズム(リソース分離ポリシー)をデプロイして、このような一般的なクロスオリジン攻撃からアプリケーションを保護できます。

特定のウェブ アプリケーションによって公開されるリソースは、他のウェブサイトではなく、そのアプリケーション自体によってのみ読み込まれるのが一般的です。このような場合は、Fetch Metadata リクエスト ヘッダーに基づいてリソース分離ポリシーをデプロイすることで、ほとんど手間をかけずにクロスサイト攻撃からアプリケーションを保護できます。

ブラウザの互換性

メタデータ取得リクエスト ヘッダーは、最新のすべてのブラウザ エンジンでサポートされています。

対応ブラウザ

  • Chrome: 76。
  • Edge: 79.
  • Firefox: 90。
  • Safari: 16.4。

ソース

背景

ウェブはデフォルトでオープンであり、アプリケーション サーバーは外部アプリケーションからの通信から自身を簡単に保護できないため、多くのクロスサイト攻撃が発生する可能性があります。典型的なクロスオリジン攻撃は、クロスサイト リクエスト フォージェリ(CSRF)です。攻撃者は、ユーザーを自分の管理するサイトに誘導し、ユーザーがログインしているサーバーにフォームを送信します。サーバーはリクエストが別のドメイン(クロスサイト)から発信されたかどうかを判断できず、ブラウザはクロスサイト リクエストに Cookie を自動的に付加するため、サーバーは攻撃者によってリクエストされたアクションをユーザーに代わって実行します。

クロスサイト スクリプト挿入(XSSI)やクロスオリジン情報漏洩などの他のクロスサイト攻撃は、CSRF と本質的に類似しており、攻撃者が制御するドキュメントに被害を受けたアプリケーションからリソースを読み込み、被害を受けたアプリケーションに関する情報を漏洩させます。アプリは信頼できるリクエストと信頼できないリクエストを簡単に区別できないため、悪意のあるクロスサイト トラフィックを破棄できません。

Fetch Metadata のご紹介

メタデータ取得リクエスト ヘッダーは、クロスオリジン攻撃からサーバーを保護するために設計された、新しいウェブ プラットフォームのセキュリティ機能です。一連の Sec-Fetch-* ヘッダーで HTTP リクエストのコンテキストに関する情報を提供することで、レスポンス サーバーはリクエストを処理する前にセキュリティ ポリシーを適用できます。これにより、リクエストの送信方法と使用されるコンテキストに基づいて、リクエストを承認または拒否するかどうかをデベロッパーが決定できるため、独自のアプリによって送信された正当なリクエストにのみ応答できます。

同一オリジン
独自のサーバーによって提供されるサイト(同じオリジン)から送信されるリクエストは引き続き機能します。 JavaScript で https://site.example/foo.json リソースに対する https://site.example からの取得リクエストにより、ブラウザは HTTP リクエスト ヘッダー「Sec Fetch-Site: same-origin」を送信します。
クロスサイト
Sec-Fetch-* ヘッダーによって HTTP リクエストに追加のコンテキストが提供されるため、悪意のあるクロスサイト リクエストはサーバーで拒否される可能性があります。 img 要素の src 属性が「https://site.example/foo.json」に設定されている https://evil.example の画像があると、ブラウザは HTTP リクエスト ヘッダー「Sec-Fetch-Site: cross-site」を送信します。

Sec-Fetch-Site

対応ブラウザ

  • Chrome: 76。
  • Edge: 79.
  • Firefox: 90。
  • Safari: 16.4。

ソース

Sec-Fetch-Site は、リクエストを送信したサイトをサーバーに伝えます。ブラウザは、この値を次のいずれかに設定します。

  • same-origin: リクエストが独自のアプリケーションによって行われた場合(site.example など)
  • same-site: リクエストがサイトのサブドメイン(bar.site.example など)から行われた場合
  • none: ユーザーがユーザー エージェントを操作したことが明示的にリクエストの原因である場合(ブックマークのクリックなど)
  • cross-site(リクエストが別のウェブサイトから送信された場合)(例: evil.example

Sec-Fetch-Mode

対応ブラウザ

  • Chrome: 76。
  • Edge: 79.
  • Firefox: 90。
  • Safari: 16.4。

ソース

Sec-Fetch-Mode は、リクエストのモードを示します。これはリクエストのタイプにほぼ対応しており、リソースの読み込みとナビゲーション リクエストを区別できます。たとえば、navigate の宛先はトップレベルのナビゲーション リクエストを示し、no-cors は画像の読み込みなどのリソース リクエストを示します。

Sec-Fetch-Dest

対応ブラウザ

  • Chrome: 80。
  • Edge: 80.
  • Firefox: 90。
  • Safari: 16.4。

ソース

Sec-Fetch-Dest は、リクエストの宛先を公開します(たとえば、script タグまたは img タグによってブラウザがリソースをリクエストした場合)。

フェッチ メタデータを使用してクロスオリジン攻撃を防ぐ方法

これらのリクエスト ヘッダーが提供する追加情報は非常にシンプルですが、追加のコンテキストにより、サーバーサイドで強力なセキュリティ ロジック(リソース分離ポリシーとも呼ばれます)を数行のコードのみで構築できます。

リソース分離ポリシーの実装

リソース分離ポリシーを使用すると、外部ウェブサイトからリソースがリクエストされるのを防ぐことができます。このようなトラフィックをブロックすることで、CSRF、XSSI、タイミング攻撃、クロスオリジン情報漏洩などの一般的なクロスサイト ウェブの脆弱性を軽減できます。このポリシーは、アプリのすべてのエンドポイントで有効にできます。これにより、独自のアプリからのすべてのリソース リクエストと、直接ナビゲーション(HTTP GET リクエスト経由)が許可されます。クロスサイト コンテキストで読み込まれるエンドポイント(CORS を使用して読み込まれるエンドポイントなど)は、このロジックをオプトアウトできます。

ステップ 1: メタデータの取得を送信しないブラウザからのリクエストを許可する

すべてのブラウザがメタデータの取得をサポートしているわけではないため、sec-fetch-site の存在を確認して、Sec-Fetch-* ヘッダーが設定されていないリクエストを許可する必要があります。

if not req['sec-fetch-site']:
  return True  # Allow this request

ステップ 2: 同一サイト間リクエストとブラウザ開始リクエストを許可する

クロスオリジン コンテキスト(evil.example など)から発生していないリクエストはすべて許可されます。具体的には、次のリクエストが対象となります。

  • 独自のアプリケーションから発生する(site.examplesite.example/foo.json をリクエストする同一オリジン リクエストが常に許可されるなど)。
  • サブドメインから送信される。
  • ユーザー エージェントに対するユーザーの操作によって明示的に発生している(直接ナビゲーションやブックマークのクリックなど)。
if req['sec-fetch-site'] in ('same-origin', 'same-site', 'none'):
  return True  # Allow this request

ステップ 3: シンプルなトップレベル ナビゲーションと iframing を許可する

他のサイトからサイトへのリンクを維持するには、シンプルな(HTTP GET)トップレベル ナビゲーションを許可する必要があります。

if req['sec-fetch-mode'] == 'navigate' and req.method == 'GET'
  # <object> and <embed> send navigation requests, which we disallow.
  and req['sec-fetch-dest'] not in ('object', 'embed'):
    return True  # Allow this request

ステップ 4: クロスサイト トラフィックの処理を目的としたエンドポイントをオプトアウトする(省略可)

場合によっては、クロスサイト読み込みを目的としたリソースをアプリが提供することがあります。これらのリソースは、パスごとまたはエンドポイントごとに除外する必要があります。このようなエンドポイントの例を次に示します。

  • クロスオリジンからアクセスすることを目的としたエンドポイント: アプリケーションで CORS が有効になっているエンドポイントを処理している場合は、これらのエンドポイントへのクロスサイト リクエストが引き続き可能になるように、リソース分離から明示的にオプトアウトする必要があります。
  • 公開リソース(画像、スタイルなど): 他のサイトからクロスオリジンで読み込む必要がある公開リソースと未認証リソースも除外できます。
if req.path in ('/my_CORS_endpoint', '/favicon.png'):
  return True

ステップ 5: ナビゲーション以外のクロスサイト リクエストをすべて拒否する

他のクロスサイト リクエストは、このリソース分離ポリシーによって拒否されるため、一般的なクロスサイト攻撃からアプリケーションを保護できます。

例: 次のコードは、単純なナビゲーション リクエストを許可しながら、悪意のある可能性のあるクロスサイト リソース リクエストを拒否する、堅牢なリソース分離ポリシーをサーバーまたはミドルウェアとして完全に実装したものです。

# Reject cross-origin requests to protect from CSRF, XSSI, and other bugs
def allow_request(req):
  # Allow requests from browsers which don't send Fetch Metadata
  if not req['sec-fetch-site']:
    return True

  # Allow same-site and browser-initiated requests
  if req['sec-fetch-site'] in ('same-origin', 'same-site', 'none'):
    return True

  # Allow simple top-level navigations except <object> and <embed>
  if req['sec-fetch-mode'] == 'navigate' and req.method == 'GET'
    and req['sec-fetch-dest'] not in ('object', 'embed'):
      return True

  # [OPTIONAL] Exempt paths/endpoints meant to be served cross-origin.
  if req.path in ('/my_CORS_endpoint', '/favicon.png'):
    return True

  # Reject all other requests that are cross-site and not navigational
  return False

リソース分離ポリシーのデプロイ

  1. 上記のコード スニペットのようなモジュールをインストールして、サイトの動作をログに記録し、モニタリングします。また、制限が正当なトラフィックに影響しないようにします。
  2. 正当なクロスオリジン エンドポイントを除外して、潜在的な違反を修正します。
  3. ポリシーに準拠していないリクエストをドロップして、ポリシーを適用します。

ポリシー違反の特定と修正

サーバーサイド コードでレポート モードでポリシーを有効にして、副作用のない方法でポリシーをテストすることをおすすめします。また、このロジックをミドルウェアに実装することもできます。または、本番環境トラフィックに適用されたときにポリシーによって発生する可能性のある違反をログに記録するリバース プロキシに実装することもできます。

Google でメタデータ取得リソース分離ポリシーを導入した経験から、ほとんどのアプリケーションはデフォルトでこのようなポリシーに対応しており、クロスサイト トラフィックを許可するためにエンドポイントを除外する必要はほとんどありません。

リソース分離ポリシーの適用

ポリシーが正当な本番環境トラフィックに影響しないことを確認したら、制限を適用して、他のサイトがリソースをリクエストできないようにし、クロスサイト攻撃からユーザーを保護できます。

関連情報