Same-Site, Same-Origin について実際に動かしながら挙動を調べてみた

今回は、名前も目的も似ていて混同されやすい Same Site と Same Origin について実際に動かしながら挙動を確認してみました
2023.05.17

西田@CX事業本部です

今回は、名前も目的も似ていて混同されやすい Same Site と Same Origin について実際に動かしながら挙動を確認してみました

何に使われるもの?

Same Origin

主に同一オリジンポリシーで使われます。同一オリジンポリシーとは、HTMLに埋め込まれたJavaScriptが、その提供元以外のリソースに対してのアクセスを制限するものです

この制限は CSRF 等の攻撃のリスクを軽減することを目的にしています

Same Site

Set-Cookieヘッダの属性の一つで、Cookieの送信先を制限するために使われます。 Set-Cookieヘッダの他の属性である domain や path と同じように設定できます

Set-Cookie: name=value; Domain=<example.jp>; Secure; HttpOnly; SameSite=Strict

Same Site も 主に同一オリジンポリシー と同じく CSRF のリスクの軽減を目的にしています

Same Origin の条件

以下の全てが一致すれば Same Origin となります。1つでも異なれば Cross Origin になります

  • スキーマ(http, https)
  • ホスト(example.jp, example1.jp)
  • ポート(80, 443)
Origin A Origin B 判定 説明
http://example.jp http://example.jp same-origin
http://example.jp https://example.jp cross-origin スキーマが異なる
https://example.jp https://www.example.jp cross-origin ホストが異なる
https://example.jp:3000 https://example.jp:8888 cross-origin ポートが異なる

Same Site の条件

以下の全てが一致すれば Same Site となります。1つでも異なれば Cross Site になります

  • スキーマ(http, https)
  • eTLD + 1 (例: ${yourdomain}.co.jp, ${yourdomain}.github.io)

eTLD(effective Top Level Domain) とは、 .com や .org など Root Zone Database に掲載されてるTLD とそのすぐ左側を合わせたものです

.co.jp.github.io など、実質的にTLDとして扱われてるものであり、これらは Public Suffix List にまとめられています

※ Same Origin と違って port の違いは許容されますが、スキーマ(http, https)が異なると実質的には port も異なることになります(http: 80, https: 443)

Site A Site B 判定 説明
https://www.example.jp https://login.example.jp same-site サブドメインが異なっても eTLD + 1 (example.jp) が同じ
http://example.jp https://example.jp cross-site スキーマが異なる

Same Origin/Site の違い

Same Origin/Site の違いをまとめてみます。主にサブドメインが same であると判定されるかどうかに違いがあります

Cross Origin は Cross Site よりも厳しい制約となっており、Cross Site なら Cross Origin になりますが、Cross Origin なら必ずしも Cross Site であるとは限りません

A B 判定 説明
https://sub1.example.jp https://sub2.example.jp cross-origin, same-site eTLD + 1 が同じだが、ホスト名が異なる
https://example.com https://example1.com cross-origin, cross-site eTLD + 1が異なる、ホスト名も異なる

Cross Origin の場合は CORS 関連のヘッダを設定し、 fetch 関数であれば credentials オプションに include を設定しないと Cookie は送信されません

Cross Site の場合はさらに SameSite 属性に None を設定する必要があります

SameSiteに設定する値

Same Site に設定できる値は Strict, Lax(デフォルト), None の3種類あります。 Cross Site リクエスト時に Strict だと Cookie は送信されません。None であれば Cookie が送信されます

デフォルトである Lax だとトップレベルナビゲーションの時のみ Cookie を送信します。トップレベルナビゲーションとは、WEBブラウザのURLバーのURLと、表示されてるページが一致する場合です。具体的な例だと、Cross Site のサイトから <img> タグで参照されている場合は Cookie は送信されず、 <a> タグでその画像のURLに遷移すると Cookie が送信されます

試してみた

簡単なサーバーを用意して、Cross Origin、Cross Siteの動きを実際に検証していきます

全体構成

  • app1.example.jp から example1.jpへのリクエストが Cross Origin, Cross Site になります
  • app1.example.jp から app2.example.jp へのリクエストが Cross Origin, Same Site になります

また、 app1.example.jp には、以下の Set-Cookie で Cookie が設定されています

Set-Cookie: test=1; SameSite=Lax

検証

app1.example.jp から他のサイトへいろんな条件下でリクエストすることで挙動を確認していきます

JavaScript で fetch を使って Cross Origin と Same Origin のサイトにアクセスする関数を用意します

// Cross Origin リクエスト
function fetchFromOther() {
  fetch("https://app2.example.jp")
}

// Same Origin リクエスト
function fetchFromOrigin() {
  fetch("/")
}

HTMLに以下の要素を記述します

  • 上記の関数を呼び出すハンドラーが設定された button 要素
  • Cross Site, Same Site 上の画像を参照する img 要素
  • img 要素の src に対しリンクで移動する a 要素
<button onclick="fetchFromOther()">fetch Other</button>
<button onclick="fetchFromOrigin()">fetch Origin</button>  

<a href="https://{{.OtherHost}}/hanako.jpeg">Other</a>
<image src="https://{{.OtherHost}}/hanako.jpeg" />

<a href="/hanako.jpeg">Origin</a>
<image src="/hanako.jpeg" />

Cross Origin に対する JavaScript からのアクセス

app1.example.jp から app2.example.jp への fetch は Cross Origin になり、CORS エラーになり、リクエストが送信されませんでした

Access to fetch at 'https://xxxx/' from origin 'https://xxx' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource. If an opaque response serves your needs, set the request's mode to 'no-cors' to fetch the resource with CORS disabled.

CORSエラーを解消し、リクエストを成功させるには、Cross Origin のレスポンスに、リクエストの元の Origin を許可するCORS関連のHTTPヘッダーを含める必要があります

Access-Control-Allow-Origin: app1.example.jp

Same Site に対する img タグの Cookie 送信

app1.example.jp に埋め込まれた app2.example.jp の画像の Cookie は送信されました

Cross Site に対する img タグの Cookie 送信

app1.example.jp に埋め込まれた example1.jp の画像の Cookie が送信されませんでした

Same Site に Cookie を送信するには、SameSite属性に None を指定する必要があり、その際に Secure 属性も設定する必要があります

Set-Cookie: test=1; SameSite=None; Secure

Cross Site に対する a タグでの画面遷移時の Cookie 送信

app1.example.jp から example1.jp の画像へのリンクでの遷移では Cookie が送信されました

但し、Cookie の SameSite 属性に Strict を設定した場合は aタグでの遷移でも Cookie が送信されませでした

Set-Cookie: test=1; SameSite=Strict

Cross Origin かつ Cross Site に対する Cookie の送信について

Cross Origin でCookieを送信するために fetch の credentials オプションに include を指定します

function fetchFromOther() {
  fetch("https://app2.example.jp", { credentials: "include" })
}

さらに、Cross Origin のレスポンスヘッダに Access-Control-Allow-Origin にリクエスト元のオリジンを指定しリクエストを許可する必要があります。また、 Access-Control-Allow-Credentials に true を指定する必要があります。 Access-Control-Allow-Credentials にtrueを指定した場合に Access-Control-Allow-Origin* アスタリスクは使えない点にご注意ください

Access-Control-Allow-Origin: https://app1.example.jp

そして、Cross Site にCookieを送信したい場合は、 Cookie の SameSite 属性に None と Secure 属性を指定する必要があります

Set-Cookie: test=1; SameSite=None; Secure

まとめ

今回はなんとく設定していた Cross Origin と Cross Siteの違いを確認しながら、実際に手を動かして挙動を理解してみました。この記事が誰かの役に立てば嬉しいです

参考