SOPとCORSを絡めたセキュリティ(1)

一生SOPを覚えられないので、ついに書くことに…

まず、「ブラウザが異なるWebサイト間の操作をどう制御しているのか」という視点で考える必要があります。
特に重要なのは、Webセキュリティには異なる目的を持った仕組みが複数存在することです。

  • SOP:異なるOrigin間の操作、特に情報の読み取りを制限する
  • CORS:異なるOriginからのレスポンスの読み取りを、一定の条件で許可する
  • CSRF対策:利用者の意図しないリクエストによる操作を防ぐ
  • Cookieの属性:Cookieがどの通信に送られるか、JavaScriptから参照できるかなどを制御する
  • CSP・CORP・COEPなど:リソースの読み込み・埋め込みや文書間の分離を制御する

なぜSOPが必要なのか

Webブラウザの基本的な性質として、ユーザは複数のWebサイトを同時に利用できます。
銀行にログインしたまま、別のWebサイトを開いている状況を考えてみます。

ブラウザは、訪問先のWebサイトが提供するJavaScriptを実行します。
もし、異なるOrigin間で自由に情報を読み取れるならば、悪意のあるサイトは、ユーザがログインしている銀行やメールサービスなどの情報を取得できてしまいます。
そこで、Webサイトごとに情報へのアクセス権限を分離するという考え方が必要になります。

これを実現する中心的な仕組みがSOPです。

同一オリジンポリシー(MDN):https://developer.mozilla.org/ja/docs/Web/Security/Defenses/Same-origin_policy

SOPは何を制限するのか

SOCは、単純に「異なるOriginへの通信を禁止する仕組み」ではありません。
MDNでは、異なるOrigin間ネットワークアクセスを大きく3種類に分類しています。

操作SOPの基本的な考え方
Write(書き込み)原則として許可
Embed(埋め込み)原則として許可
Read(読み取り)原則として制限

この分類がSOPの理解におけるもっとも重要なポイントです。

Write:異なるOriginにリクエストを送信する(原則許可)

<form action="https://bank.example.com/transfer" method="POST"> # 今のページはbank.example.comではないものとする
  <input name="amount" value="10000">
  <button type="submit">送信</button>
</form>

例えば、別サイトに対するHTMLフォームのPOST送信です。
ブラウザは、このようなCross-Originのフォーム送信を原則として許可しています。(この辺りはプリフライトの要否にかかわる条件が関係します。)

したがって、SOPはリクエストの送信自体を全面的に禁止する仕組みではありません。
Cookieの属性、CSP、CORS Preflightなど、他の仕組みによって送信が制約されることはあります。

Embed:異なるOriginのリソースを埋め込む(原則許可)

例えば、次のようなHTMLです。

<img src="https://images.example/photo.png">

これもCross-Originですが、通常は画像を表示できます。
Embedは、別オリジンのリソースをページの一部として表示することです。

SOPが異なるOriginからの埋め込みを全面的に禁止していた場合、多くのWebサイトで画像や外部コンテンツを利用できなくなります。

ただし、画像を表示できることと、そのリソースが持つ情報にプログラムからアクセスすることは異なり、後者はできません。
例えば、ログイン後のプロフィール画像がある場合、その画像情報を読み込んだうえで、ピクセル情報の解析をしたら、個人情報が取得できてしまう可能性があります。

Read:異なるOriginの情報を読み取る(原則制限)

例えば、次のJavaScriptです。

const response = await fetch(
  "https://bank.example.com/api/balance"
);

const data = await response.json();
console.log(data);

この場合、CORSなどによる許可がなければ、JavaScriptはレスポンス内容を取得できません。
(そのブラウザまではレスポンスが届いてはいます。)

本リクエストが送信され、HTTPレスポンスがブラウザに届いていても、JavaScriptへの公開が拒否される場合があります。

なお、SOPはHTTPレスポンスだけでなく、異なるOriginのiframeのDOMや、localStorageなどのストレージへのアクセスも制限します。

CORSはなぜ必要になったのか

SOPによる読み取り制限は重要ですが、現代のWebアプリケーションでは異なるOrigin間でAPIを利用することがあります。

この2つは異なるOriginなので、SOPの制限対象です。
しかし、APIサーバ側が、「app.example.comからのJavaScriptによる読み取りを許可したい」と考えるケースは普通にあります。
その許可をブラウザに伝える仕組みがCORSです。

Access-Control-Allow-Origin: https://app.example.com

ここで重要なのは、CORSはAPIにアクセスする利用者の認証・認可を行うものではないということです。
CORSが許可するのは、対象OriginのWebページで実行されるJavaScriptが、CORSの対象となるレスポンスを利用できるということです。
そのため、CORSが許可したOriginのJavaScriptにレスポンスを読まれても問題がないかを検討する必要があります

SOP/CORSを理解するうえで重要な「Origin」と「Site」の違い

ここはCookieやCSRF対策を理解する際に重要です。

OriginとSiteでは、判定基準が違います。

例えば、基準となるURLが https://app.example.com の場合、次のようになります。

比較対象(基準:https://app.example.com )Same-OriginSame-Site
https://app.example.com/path○○
https://api.example.com×○
https://app.example.com:8443×○
http://app.example.com××
https://other.example.net××

※ Same-Siteは現在のCookie判定で使われるschemeful same-siteを前提としています。

SOPやCORSはOriginを基準にしますが、CookieのSameSiteはSiteを基準にしています。
この違いによって、サブドメイン間のAPI呼び出しはCross-Originでも、Cookieの判定ではSame-Siteになるという状況が生まれます。

リクエスト送信とレスポンス読み取りを分けて理解する

CORSの挙動は、具体的なリクエストで整理すると分かりやすくなります。

Preflightは、特定のHTTPメソッドやヘッダーを伴うCross-Originリクエストに対して、ブラウザが本リクエストの前に行う確認です。
ただし、Preflightは認証・認可の仕組みではありません。 Preflightを通過したリクエストであっても、サーバは通常どおり認証・認可を実施しなければなりません。

なぜSOPがあってもCSRFが成立するのか

ここで、SOPの根本的な設計思想に戻ります。

SOPはCross-Originの読み取りを制限しますが、フォームによる送信などは原則として許可します。

この性質から、以下のような攻撃が問題になります。

これがCSRFの典型的な考え方です。

したがって、CSRF対策としては以下のような、そもそも「リクエストを送らせない/サーバが処理しない仕組み」が必要になります。

  • CSRFトークンによるリクエストの正当性確認
  • CookieのSameSite属性
  • Originヘッダーの検証
  • Sec-Fetch-SiteなどのFetch Metadataを利用したリクエスト制御

なお、SameSiteだけではSame-Siteのサブドメインから発生する攻撃などをすべて防げるわけではありません。

関連するセキュリティ技術を体系的に整理する

この辺りは次の記事でさらに理解していきます。

分類技術主な目的
Origin間の分離SOP(Same-Origin Policy(同一オリジンポリシー))異なるOriginからの読み取りやDOMアクセスを制限
読み取りの例外許可CORS(Cross-Origin Resource Sharing(オリジン間リソース共有)Cross-Originのレスポンス利用を許可
意図しない操作の防止CSRFトークン正当な操作要求か検証
Cookieの送信制御SameSiteCross-SiteリクエストでCookieを送る条件を制御
Cookieの参照制御HttpOnlyJavaScriptによるCookieの直接参照を禁止
リソース取得制御CSPスクリプトや通信先などを制限
他サイトへの埋め込み防止CSP frame-ancestors / X-Frame-Optionsiframeなどへの埋め込みを制限
リソースの公開制御CORP(Cross-Origin Resource Policy(オリジン間リソースポリシー))他Origin/Siteによるno-corsリソース読み込みを制限
文書のリソース読み込み制御COEP(Cross-Origin Embedder Policy(オリジン間埋め込みポリシー))Cross-OriginリソースにCORSやCORPによる許可を要求するなど
ウィンドウ間の分離COOP(Cross-Origin Opener Policy(オリジン間オープナーポリシー))異なる文書間のブラウジングコンテキストの関係を分離
サーバ側の要求制御Fetch Metadataリクエストの発生元との関係などに応じて許可・拒否
Origin間の明示的通信postMessage異なるOriginのウィンドウ間でメッセージを交換

CSPとCORSの違い

CSPは、基本的に自分のページが何を読み込んだり、どこへ通信したりできるかを制約します。
一方、CORSはアクセス先サーバがCross-OriginのJavaScriptによるレスポンス利用を認めるかを制御します。

例えば、CSPに次の設定があれば、

Content-Security-Policy: connect-src 'self'

そのページのJavaScriptによる異なるOriginへのfetch()などは、通常ブラウザによってブロックされます。リクエスト送信自体が抑止されるケースです。
これは、CORSの不許可によってレスポンスを読めなくする動作とは異なります。