Same Origin Policy

読み:せーむおりじんぽりしー / 英語表記:Same Origin Policy

Webブラウザが、ある出どころから読み込んだ文書やスクリプトと、別の出どころの資源とのやり取りを制限するセキュリティ上の仕組みである。出どころであるオリジンはURLのスキーム・ホスト・ポート番号の組で表し、すべてが一致する場合だけ同じオリジンとして扱う。

イマシルちゃんの解説

ブラウザでは、ネットバンキングと初めて訪れたサイトを同時に開いていることもあるんだ。
もし初めてのサイトのスクリプトが、ネットバンキングの画面の中身を自由に読めたら大変だよね。
そこでブラウザは、URLの先頭にある「https」などの通信方式とホスト名、接続先の番号の組を「オリジン」として区別しているよ。
Same Origin Policyは、オリジンが違う相手のデータをスクリプトから勝手に読めないようにする、ブラウザのセキュリティの決まりなんだ。
リンクで移動したり画像を表示したりはできるけど、返ってきた中身をのぞくことは原則できないの。
だから怪しいサイトを開いてしまっても、ほかのサイトの情報が持ち出されにくくなるんだよ。

より深い解説

なぜブラウザに制限が必要なのか

ブラウザは、利用者がログインしているサイトへの要求にCookieなどを自動で付ける。そのため、罠のサイトに置かれたスクリプトが利用者のブラウザからWebメールや社内システムへ要求を送ると、その要求は利用者本人の権限で処理されてしまう。社内システムのように、インターネットから直接届かない場所にあるサーバへも、利用者のブラウザを経由すれば要求が届く。

ここで応答の中身までスクリプトが読めれば、メールの本文や社内の情報を攻撃者のサーバへ送り出せる。Same Origin Policy(同一オリジンポリシー)は、この「別の出どころから返ってきた内容を読む」ことを止めて、サイト同士を隔離する。MDNの解説も、ログイン中のWebメールやイントラネットのデータを持ち出させないことを目的として挙げている。

この仕組みを実施するのはサーバでもファイアウォールでもなく、利用者のWebブラウザである。ブラウザの外で動くプログラムや、ネットワーク機器の通信制御とは関係がない。

オリジンの決まり方

オリジンの考え方はRFC 6454(The Web Origin Concept)で整理され、現在のブラウザの動作はWHATWG HTML Standardで定められている。オリジンはURLのスキーム、ホスト、ポート番号の3つの組である。パスやクエリ文字列は含まない。

URLに番号が書かれていない場合は、通信方式ごとの既定値を使う。HTTPSなら443、HTTPなら80である。そのため、HTTPSのURLに既定値の番号をわざわざ書いても、書かない場合と同じオリジンになる。

次は https://shop.example.jp/cart/index.html を基準にした独自の比較例である。

比べるURL判定理由
https://shop.example.jp/item/list.html同一オリジンパスだけが異なる
https://shop.example.jp/cart/?id=5同一オリジンクエリ文字列だけが異なる
http://shop.example.jp/cart/別オリジンスキームが異なる
https://api.example.jp/cart/別オリジンホストが異なる
https://shop.example.jp:8443/cart/別オリジンポート番号が異なる

ホストは文字列として一致するかで判断する。api.example.jp と shop.example.jp は親のドメインが同じでも別のオリジンである。同じ組織のサーバかどうか、同じIPアドレスかどうかも判定には使わない。

オリジンを文字列で表すと、https://shop.example.jp のように末尾のパスを持たない形になり、既定値のポートは省略される。これは、別オリジンへの要求に付くOriginヘッダーの値の形でもある。

特別なオリジン

data: で始まるURLの文書や、制限付きの iframe(sandbox属性)の中の文書は、どのURLとも一致しない特別なオリジンを持つ。HTML Standardではこれをopaque origin(不透明なオリジン)と呼ぶ。文字列で表すと null になり、自分自身と以外は同じオリジンと判定されない。

ローカルのファイルを開く file: のURLの扱いはブラウザに依存する。現在の多くのブラウザは、同じフォルダのファイル同士も別のオリジンとして扱う。そのため、手元のHTMLファイルから別のファイルを読み込もうとして失敗することがある。

制限される操作と許される操作

Same Origin Policyは、別オリジンとの通信をすべて止めるわけではない。MDNは別オリジンとのやり取りを次の3種類に分けている。

種類例扱い
書き込みリンクでの移動、リダイレクト、フォームの送信通常は許される
埋め込み<script src>、<img>、<link rel="stylesheet">、<video>、<iframe>通常は許される
読み取りスクリプトによる応答の本文の参照原則として禁止される

Webは、別のサイトの画像を表示したり、別のサイトへフォームを送ったりすることで成り立ってきた。これらを止めると多くのサイトが動かなくなるため、制限の中心は「スクリプトが中身を読む」ことに置かれている。

埋め込みには注意が要る。<script src> で読み込んだスクリプトは、読み込んだ側のページのオリジンで実行される。外部のスクリプトを埋め込むことは、その提供元にページと同じ権限を与えることを意味する。また画像の縦横の大きさのように、埋め込みを通じて一部の情報が漏れることもある。

スクリプトから見える範囲

ページのスクリプトから別オリジンの資源を扱う代表的な場面と、その制限をまとめる。

場面同じオリジンの場合別オリジンの場合
fetch() や XMLHttpRequest の応答本文やヘッダーを読める相手のサーバが許可しない限り読めない
iframe の中の文書(DOM)中の要素を読み書きできる中の要素に触れられない
Web StorageやIndexedDB同じ保存領域を使う保存領域が分かれ、読み書きできない
別のウィンドウの操作自由に操作できるごく一部の操作だけができる

別オリジンの応答を読めるようにする標準の手段がCORSである。データを持つサーバ自身がHTTPのヘッダーで「このオリジンになら見せてよい」と示し、ブラウザがそれを確認する。許可の手順や関係するヘッダーは、専用の記事で詳しく扱う。

別オリジンのウィンドウや iframe と情報を交換するには、postMessage を使う。これは相手の内部を直接触る代わりに、メッセージを送り合う方式である。受信側は送信元のオリジン(event.origin)を確かめ、送信側は宛先のオリジンを * ではなく具体的に指定する。MDNの解説は、この確認を怠ると情報の漏えいやXSSにつながると警告している。

Cookieの範囲はオリジンと一致しない

Cookieは、Same Origin Policyとは別の古い規則で送り先が決まる。RFC 6265は、Cookieがポートやスキームによる隔離を提供しないと明記している。同じホストの別のポートで動くサービスは、同じCookieを受け取り得る。

さらにCookieはDomain属性で親のドメインを指定できる。shop.example.jp が設定した example.jp 向けのCookieは、api.example.jp へも送られる。オリジンとしては別でも、Cookieの範囲では同じ扱いになる場面がある。

これと関連して「サイト」という単位もある。HTML Standardでは、スキームと登録可能ドメイン(example.jp のように、登録者が取得した単位のドメイン)が同じものを同一サイトと呼ぶ。CookieのSameSite属性はこの単位で判断する。同一サイトは同一オリジンより広い範囲なので、api.example.jp と shop.example.jp は同一サイトだが別オリジンである。

同一オリジンポリシーで防げない攻撃

Same Origin Policyは、別オリジンの中身を読ませない仕組みである。そのため、次の攻撃は別の対策が必要になる。

攻撃防げない理由主な対策
クロスサイトスクリプティング(XSS)注入されたスクリプトは被害サイトと同じオリジンで動く出力時のエスケープ、CSP
CSRFフォームの送信のような書き込みは別オリジンからでも届く推測できないトークン、SameSite属性
クリックジャッキング別オリジンのページを iframe で重ねて表示すること自体は許されるiframeへの表示を禁止する応答ヘッダー

XSSではスクリプトが正規のサイトの一部として実行されるため、Same Origin Policyはむしろ攻撃者のスクリプトに正規サイトの権限を与える方向に働く。フォームの送信などを悪用する攻撃では、攻撃者が応答を読む必要がない。要求がサーバに届き、処理されれば被害が成立する。

クリックジャッキングは、透明にした別オリジンのページを罠のページに重ね、利用者に意図しないボタンを押させる攻撃である。ページを iframe に表示させない応答ヘッダーとして、X-Frame-Options の DENY・SAMEORIGIN や、CSPの frame-ancestors がある。Chromeの開発者向け資料は、frame-ancestors の方が埋め込みを許す親を細かく指定できるとしている。

制限を緩める方法と避けるべき方法

業務では、ページとAPIを別のサーバに分けるなど、別オリジンとのやり取りが必要になる。どの方法で制限を緩めるかを設計段階で決める。

方法仕組み注意点
CORSサーバがヘッダーで読み取りを許可する許可するオリジンを必要な相手に絞る
postMessageウィンドウ間でメッセージを送る送信元と宛先のオリジンを必ず確かめる
中継サーバで同一オリジンにするAPIを同じホストのパス配下に見せる中継の負荷と経路の設計が必要になる
親ドメインへの書き換え両方のページが親ドメインに合わせる非推奨であり新しく使わない

リバースプロキシを使う方法では、例えば https://www.example.jp/api/ への要求をAPIサーバへ中継する。ブラウザから見ればページもAPIも同じオリジンになるため、CORSの設定自体が不要になる。

document.domain は、サブドメイン同士のページが共通の親ドメインに書き換えて、互いに操作できるようにする古い方法である。HTML Standardは、これがSame Origin Policyの保護を弱めるとして使用を避けるよう求め、postMessage() などへの移行を勧めている。

かつては <script src> の埋め込みが許されることを利用し、別オリジンのデータをスクリプトとして読み込ませるJSONPという手法も使われた。提供元にページ内でのスクリプト実行を任せる方式であり、現在はCORSで置き換えるのが一般的である。

設計と障害の切り分け

開発環境で http://localhost:3000 の画面から http://localhost:8080 のAPIを呼ぶと、ホストが同じでもポートが違うため別オリジンになる。本番環境でも、HTTPのページからHTTPSのAPIを呼ぶ構成や、www と api のようにホスト名を分ける構成は別オリジンである。構成図を見たら、まずブラウザが表示しているページのオリジンと、要求先のオリジンを書き出して比べる。

観測した状態確認すること
ブラウザのコンソールにCORSのエラーが出るページと要求先のオリジンの違い、相手のサーバが返す許可のヘッダー
サーバのログには要求が記録されているのに画面に出ない応答の読み取りがブラウザで止められていないか
iframe の中を操作するスクリプトが失敗する親と子のオリジンの違い、postMessageへの置き換え
ファイルを直接開くと動き、Webサーバ経由だと動かない(またはその逆)file: のオリジンの扱い、要求先のURL
別のサブドメインにCookieが送られているCookieのDomain属性の指定範囲

ここで大切なのは、Same Origin Policyによる失敗は「通信できない」障害とは限らない点である。要求がサーバに届いて処理され、応答も返っているのに、ブラウザがスクリプトへ渡さないだけということがある。サーバ側のログとブラウザの開発者ツールの両方を見ると、どこで止まったかを区別できる。

午後での問われ方

参考資料

この用語が出てくる記事

↑