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による失敗は「通信できない」障害とは限らない点である。要求がサーバに届いて処理され、応答も返っているのに、ブラウザがスクリプトへ渡さないだけということがある。サーバ側のログとブラウザの開発者ツールの両方を見ると、どこで止まったかを区別できる。
午後での問われ方
- 構成図のURLから、ページとAPIが同じオリジンかどうかを判定させる。ホスト名・スキーム・ポートのどれが違うかを根拠に答える
- 別オリジンのAPIを呼ぶ構成で、サーバ側に追加すべき設定や、リバースプロキシで同一オリジンにする設計を問う
- XSSで盗まれる情報の範囲を、注入されたスクリプトが動くオリジンと結び付けて説明させる
- CSRFやクリックジャッキングがSame Origin Policyだけでは防げない理由を問う
- オリジンとサイトの違いや、Cookieの送信範囲との違いを区別させる
参考資料
- RFC 6454:The Web Origin Concept
- WHATWG HTML Standard:Origin
- MDN:Same-origin policy
- RFC 6265:HTTP State Management Mechanism