仕組みから学ぶ Web
基礎読了目安 12#オリジン#セキュリティ#SOP#ブラウザ

同一オリジンポリシー — ブラウザが最初から持っている壁

なぜ他のサイトの JavaScript から自分のページのデータを読めないのか。この制約がなければ Web はどうなっていたか。

この記事の進み方

What — オリジンとは何を指すか

オリジンは スキーム + ホスト + ポート の3点セットです。1つでも違えば別のオリジンになります。

スキーム
http か https か。同じホストでも、http と https は別オリジン。
ホスト
ドメイン名。app.example.com と api.example.com は別オリジン。サブドメインが違えば別。
ポート
3000 と 8080 は別オリジン。省略時は http が 80、https が 443。

https://app.example.com を基準に、何が同じで何が違うかを見てください。

比較対象同一オリジンか理由
https://app.example.com/other/path✅ 同一パスは判定に含まれない
https://app.example.com:443/✅ 同一https の既定ポートは 443
http://app.example.com❌ 別スキームが違う
https://api.example.com❌ 別ホストが違う
https://example.com❌ 別サブドメインの有無も別扱い
https://app.example.com:8443❌ 別ポートが違う

何が禁止され、何が許されているか

Compare同一オリジンポリシーが止めるもの・止めないもの

別オリジンのリソースの「中身を読む」こと。JavaScript からの読み取りが対象。

// 別オリジンの iframe の中身
iframe.contentDocument        // null

// 別オリジンへの fetch のレスポンス
await fetch('https://other.com/api')   // CORS ヘッダがなければ読めない

// 別オリジンの Cookie / localStorage
// そもそもアクセスする手段がない
iframe の DOM
読めない
fetch のレスポンス
既定では読めない
localStorage
オリジンごとに完全分離
canvas に描いた別オリジン画像
ピクセルを読めない
画像の表示
スクリプトの実行
フォームの送信
iframe での表示
  • 「読めない」だけで、リクエストが飛んでいないわけではない点が重要。
  • サーバー側の設定では解除できない。解除できるのは CORS など、ブラウザが用意した例外の仕組みだけ。

「読めない」が制約の中心。送ることや表示することは許されている。この非対称さを理解していないと、CSRF がなぜ成立するのかも分からなくなる。

確認 — ここまで読めたか

iframe の contentDocument が null になります。サーバー側のヘッダ設定で解除できますか?

まず選ぶ(解答例は a〜d の記号で説明します)

Why — この壁がなかったら何が起きるか

想像してみてください。同一オリジンポリシーが存在しない世界です。

あなたがネットバンキングにログインしたまま、別のタブで悪意のあるサイトを開きます。そのサイトの JavaScript は、こう書けます。

// もし同一オリジンポリシーがなければ、これが動く
const res = await fetch('https://bank.example.com/accounts');
const balance = await res.json();   // 残高が読める
sendToAttacker(balance);

const iframe = document.createElement('iframe');
iframe.src = 'https://mail.example.com/';
document.body.append(iframe);
// iframe.contentDocument.body.innerText で、メールの全文が読める

ブラウザは Cookie を自動で送るので、リクエストはあなたとしてログイン済みの状態で飛びます。残高も、メールも、SNS のダイレクトメッセージも、開いているだけで読み放題になる。

同一オリジンポリシーは、この破滅を防ぐためにブラウザが既定で持っている壁です。サーバーが何も設定しなくても、最初から有効になっている。

なぜサーバーの設定で外せないのか

この壁が守っているのは、サーバーではなく利用者です。

もしサーバー側の設定で外せるとしたら、攻撃者は自分のサーバーで「全オリジンからの読み取りを許可する」と設定するだけで済みます。しかし攻撃者が読みたいのは自分のサーバーのデータではなく、銀行のデータです。

だから、読み取りを許可できるのは読まれる側(銀行)だけであり、その許可を伝える仕組みが CORS です。そして許可がない限り、ブラウザは読ませません。

壁を越えるために用意された抜け道

読み取りが必要な正当なケースもあるので、ブラウザは限定的な例外を用意しています。

CORS
読まれる側のサーバーが Access-Control-Allow-Origin ヘッダで「このオリジンには読ませてよい」と宣言する。最も一般的な手段。
postMessage
オリジンをまたいで、明示的にメッセージだけを送り合う。iframe や別ウィンドウとの連携に使う。DOM は触れないが、データの受け渡しはできる。
document.domain
サブドメイン間で壁を下げる古い仕組み。危険なため廃止が進んでいる。新規には使わない。
CORP / COEP
逆に、埋め込まれる側が「このリソースを他オリジンから使わせない」と宣言する仕組み。壁を高くする方向。

冒頭の事故の正解は postMessage でした。iframe の DOM を直接触ることは原理的に不可能ですが、両者が合意してメッセージをやり取りする設計にすれば連携できます。

// 親から
iframe.contentWindow.postMessage({ type: 'fill', value: 'x' }, 'https://b.example.com');

// iframe 側
window.addEventListener('message', (e) => {
  if (e.origin !== 'https://a.example.com') return;   // 送信元の検証は必須
  if (e.data.type === 'fill') input.value = e.data.value;
});

e.origin の検証を省くと、任意のサイトから操作できる穴になります。壁を越える仕組みを使うときは、越えてよい相手を自分で確認する責任が発生するということです。

確認 — ここまで読めたか

同一オリジンポリシー下でも許されているものはどれですか?

まず選ぶ(解答例は a〜d の記号で説明します)

演習 — まず自分で判断する

解説を読む前に、まず自分で判断してみてください。ここで一度詰まっておくと、 次の節の判断軸が「なるほど」ではなく「そう来たか」に変わります。

演習 — 設計判断を問う

この構成のうち、ブラウザに止められるのはどれですか?

与えられた条件
  • 自社サイト https://shop.example.com に、次の要素を置こうとしている
  • ① https://cdn.other.com/chart.js を script タグで読み込み、実行する
  • ② https://api.example.com/orders に fetch でアクセスし、JSON を読む
  • ③ https://blog.example.com を iframe で埋め、その中の要素の高さを JS で測る
  • ④ https://payment.other.com/checkout へ form の POST で遷移する
  • ⑤ https://img.other.com/logo.png を canvas に描き、ピクセルの色を取得する
この軸で考える
  • · オリジンの3要素(スキーム・ホスト・ポート)で、どれが一致しているか
  • · その操作は「読み取り」か、「送信・表示」か
  • · 同じ会社のドメインであることに、ブラウザは意味を見出すか
まず選ぶ(解答例は a〜d の記号で説明します)

演習 — 説明できるか

「iframe の中を直接操作したい」という要望に、なぜできないかと代わりの方法を説明してください

与えられた条件
  • 相手はサーバーの設定で何とかなると考えている
  • 2つの社内システムを統合する場面
  • 代替案まで示して前に進めたい

読み終わりましたか?

読了にすると、これを前提とする記事がロードマップで開放されます。

この記事を前提にしている記事