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

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

ウェブリソースの分離が重要な理由

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

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

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

ブラウザの互換性

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

対応ブラウザ

  • 76
  • 79
  • 90
  • 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 リクエストの追加コンテキストにより、悪意のあるクロスサイト リクエストがサーバーで拒否されることがあります。 https://evil.example の画像で、img 要素の src 属性が「https://site.example/foo.json」に設定されていると、ブラウザは HTTP リクエスト ヘッダー「Sec-Fetch-Site: Cross-site」を送信します。

Sec-Fetch-Site

対応ブラウザ

  • 76
  • 79
  • 90
  • 16.4

ソース

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

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

Sec-Fetch-Mode

対応ブラウザ

  • 76
  • 79
  • 90
  • 16.4

ソース

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

Sec-Fetch-Dest

対応ブラウザ

  • 80
  • 80
  • 90
  • 16.4

ソース

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

フェッチ メタデータを使用してクロスオリジン攻撃から保護する方法

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

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

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

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

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

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

ステップ 2: 同一サイトからのリクエストとブラウザで開始されたリクエストを許可する

クロスオリジン コンテキスト(evil.example など)から発信されていないリクエストはすべて許可されます。具体的には次のようなリクエストが該当します。

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

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

他のサイトから引き続きリンクできるようにするには、シンプルな(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 でメタデータ リソース分離ポリシーをロールアウトした経験から、ほとんどのアプリはデフォルトでこのようなポリシーに対応しており、クロスサイト トラフィックを許可するためにエンドポイントを除外する必要はほとんどありません。

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

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

関連情報