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

CSRF、XSSI、クロスオリジンの情報漏洩を防ぐ。

ウェブリソースの分離を重視すべき理由

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

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

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

ブラウザの互換性

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

対応ブラウザ

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

ソース

背景

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

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

メタデータ取得の概要

メタデータ取得リクエスト ヘッダーは、サーバーをクロスオリジン攻撃から守るために設計された新しいウェブ プラットフォームのセキュリティ機能です。一連の 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 でメタデータ取得リソース分離ポリシーをロールアウトしてきた経験から、ほとんどのアプリケーションはデフォルトでこうしたポリシーに対応しており、クロスサイト トラフィックを許可するためにエンドポイントを除外することはほとんどありません。

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

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

関連情報