[Browser Security] Same-Origin Policy (SOP) を正しく理解したい
Same-Origin Policy (SOP) とは
Same-Origin Policy(同一オリジンポリシー、以下SOP)とは、別オリジン(クロスオリジン)のリソースへのアクセスを制限するWebブラウザのセキュリティ機構です。
オリジンの定義
オリジンは以下の3つの要素の組み合わせで定義されます:
- スキーム(プロトコル): http、https など
- ホスト(ドメイン): example.com、sub.example.com など
- ポート番号: 80、443、3000 など
これら3つがすべて一致する場合を「同一オリジン」、一つでも異なる場合を「クロスオリジン」と呼びます。
オリジンの判定例
https://example.com:443/page
を基準とした場合:
URL | 同一オリジン? | 理由 |
---|---|---|
https://example.com/other | ✅ 同一 | すべて一致(ポート443は省略) |
http://example.com/page | ❌ クロス | スキームが異なる(http vs https) |
https://sub.example.com/page | ❌ クロス | ホストが異なる |
https://example.com:8080/page | ❌ クロス | ポートが異なる |
SOPが制限するアクセス方法
SOPは主に以下の2つのアクセス方法を制限します:
- ネットワークアクセス: fetch API、XMLHttpRequest などによるHTTPリクエスト
- JavaScript APIを用いたドキュメントの参照: window.open()、iframe のcontentWindowなどを通じた別ウィンドウへのアクセス
なぜSOPが必要なのか
SOPは以下のようなセキュリティ上の脅威から保護する役割を果たします:
- 機密情報の漏洩防止: 悪意のあるサイトが他サイトのデータを勝手に読み取ることを防ぐ
- XSS攻撃の影響範囲の制限: クロスサイトスクリプティングの被害を限定的にする
- CSRF攻撃の部分的な緩和: レスポンスの読み取りを制限することで攻撃の成功を確認できなくする
クロスオリジンアクセスの制限パターン
ネットワークを介したリソースアクセスにおいて、SOPがどのようにアクセスを制限するのか、3つのパターンに分けて解説します。
[同一オリジン?]
├─ はい → ほぼ制限なし
└─ いいえ(クロスオリジン)
├─ 「書き込み」(リンク/リダイレクト/フォーム送信) → 多くは許可*
├─ 「埋め込み」(<img>, <script>, <link>, <video>, <iframe> 等) → 表示は可/中身は読めない
└─ 「読み取り」(fetch/XHR でレスポンス本文やJSONを読む) → 原則NG(CORSで緩和可)
*一部メソッドやヘッダはプリフライトが必要
1. 書き込み(Write)- 基本的に許可
「書き込み」という表現は誤解を招きやすいですが、これは「別オリジンのリソースを書き換える」ことではなく、HTTPリクエストを送信する行為を指します。
許可される書き込みの例
- リンクによる画面遷移:
<a href="https://other-origin.com">
でのページ遷移 - フォーム送信:
<form action="https://other-origin.com/api" method="POST">
によるデータ送信 - 単純なリクエスト: GET、POST(特定のContent-Typeのみ)、HEADメソッド
重要な点は、リクエストは送信できるが、レスポンスの内容は読み取れないということです。
2. 埋め込み(Embed)- 表示は可能、内容の読み取りは不可
埋め込みリソースは表示や実行は可能ですが、JavaScriptから内容を読み取ることはできません。
許可される埋め込みの例
- 画像:
<img src="https://other-origin.com/image.jpg">
- 表示は可能だが、canvas経由でピクセルデータは読めない - スクリプト:
<script src="https://other-origin.com/script.js">
- 実行は可能だが、ソースコードは読めない - スタイルシート:
<link rel="stylesheet" href="https://other-origin.com/style.css">
- 適用は可能だが、CSSルールは読めない - 動画・音声:
<video>
,<audio>
- 再生は可能 - iframe:
<iframe src="https://other-origin.com">
- 表示は可能だが、contentWindowへのアクセスは制限される
3. 読み取り(Read)- 原則禁止
JavaScriptから別オリジンのリソースの内容を読み取ることは原則として禁止されています。
ブロックされる読み取りの例
// ❌ クロスオリジンのデータ取得は失敗する
fetch('https://other-origin.com/api/data')
.then(response => response.json()) // ここでエラー
.catch(error => console.error('CORS error:', error));
// ❌ XMLHttpRequestも同様
const xhr = new XMLHttpRequest();
xhr.open('GET', 'https://other-origin.com/api/data');
xhr.send(); // リクエストは送信されるが、レスポンスは読めない
JavaScript APIによる別オリジンのドキュメントアクセス
iframeやwindow.open()で開いた別オリジンのウィンドウに対して、JavaScriptからアクセスしようとすると制限がかかります。
アクセスが制限される例
// 別オリジンのiframe
const iframe = document.getElementById('cross-origin-iframe');
// ❌ 以下のアクセスは失敗する
iframe.contentWindow.document; // エラー
iframe.contentWindow.localStorage; // エラー
// ✅ 一部のプロパティは許可される
iframe.contentWindow.postMessage('Hello', '*'); // OK
iframe.contentWindow.location.href = 'https://example.com'; // OK(書き込みのみ)
window.postMessage() による安全な通信
別オリジン間で安全に通信するには、postMessage
APIを使用します:
// 送信側
otherWindow.postMessage('Hello from origin A', 'https://origin-b.com');
// 受信側
window.addEventListener('message', (event) => {
if (event.origin !== 'https://origin-a.com') return;
console.log('Received:', event.data);
});
SOPとCSRF攻撃の関係
悪意のあるリクエストは防げるか?
結論:SOPだけではCSRF攻撃を完全に防ぐことはできません。
SOPは主にレスポンスの読み取りを制御する仕組みであり、リクエストの送信自体は多くの場合制限されません。これが CSRF(Cross-Site Request Forgery)攻撃の脆弱性につながります。
CSRF攻撃の仕組み
- ユーザーが正規サイト(bank.com)にログイン中
- 悪意のあるサイト(evil.com)にアクセス
- evil.com のスクリプトがユーザーのブラウザから bank.com へリクエストを送信
- ブラウザは自動的にCookieを付与してリクエストを送る
- bank.com は正規のリクエストと判断して処理してしまう
<!-- evil.com の悪意のあるコード -->
<form id="evil-form" action="https://bank.com/transfer" method="POST">
<input type="hidden" name="to" value="attacker-account">
<input type="hidden" name="amount" value="10000">
</form>
<script>
// ユーザーの意図しない送金リクエストが送信される
document.getElementById('evil-form').submit();
</script>
CSRF対策
SOPだけでは不十分なため、以下の対策が必要です:
- CSRFトークン: リクエストごとに検証用のトークンを含める
- SameSite Cookie: Cookieの送信を同一サイトからのリクエストに限定
- Originヘッダーの検証: リクエスト元のオリジンを確認
- カスタムヘッダーの要求: プリフライトリクエストを強制
クロスオリジンリソースの読み込み方法
CORS(Cross-Origin Resource Sharing)
別オリジンのリソースを読み込む正当な必要がある場合は、CORSを使用します。CORSは、サーバー側で明示的に許可することで、SOPの制限を緩和する仕組みです。
サーバー側の設定例
// Node.js/Express の例
app.use((req, res, next) => {
// 特定のオリジンを許可
res.header('Access-Control-Allow-Origin', 'https://trusted-site.com');
// 認証情報を含むリクエストを許可
res.header('Access-Control-Allow-Credentials', 'true');
// 許可するHTTPメソッド
res.header('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE');
// 許可するヘッダー
res.header('Access-Control-Allow-Headers', 'Content-Type, Authorization');
next();
});
クライアント側のリクエスト例
// CORSが有効な場合、このリクエストは成功する
fetch('https://api.example.com/data', {
method: 'GET',
credentials: 'include', // Cookieを含める場合
headers: {
'Content-Type': 'application/json',
}
})
.then(response => response.json())
.then(data => console.log(data))
.catch(error => console.error('Error:', error));
その他の回避方法
- プロキシサーバー: 同一オリジンのサーバーを経由してリクエストを中継
- JSONP:
<script>
タグを利用した古い手法(セキュリティリスクあり、非推奨) - サーバーサイドでの取得: バックエンドでAPIを呼び出し、フロントエンドに返す
まとめ
Same-Origin Policyは、Webセキュリティの基盤となる重要な仕組みです。主なポイントは:
- オリジンはスキーム、ホスト、ポートの組み合わせで決まる
- クロスオリジンアクセスは書き込み・埋め込み・読み取りの3パターンで制限が異なる
- SOPだけではCSRF攻撃を完全に防げないため、追加の対策が必要
- 正当な理由でクロスオリジンアクセスが必要な場合はCORSを使用する
Webアプリケーション開発において、SOPの仕組みを正しく理解することは、セキュアなアプリケーションを構築するために不可欠です。