SiteとOriginの比較＆SameSite cookiesによるCSRF対策

## 概要
- `Origin`は**スキーム+ホスト(+ポート)**
- `Site`は**登録可能なドメイン(registrable domain)**
- サブドメインが信頼できない場合、`SameSite cookies`は**CSRF対策として不十分**

## ざっくりと

例えば、`http://example.com/app/index.html`というURLがあったとします。
乱暴に言ってしまうなら、この中でOriginとSiteは以下のようになります。
![example.comがSiteでhttp://example.comがOriginに当たる](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/316990/9f736f8b-cf70-544b-5b8d-36a40a9d3cf5.png "URL中のSiteとOrigin")
ここで大切なのは**OriginはSiteを包含する概念である**ということです。

## Origin

OriginはURLの**スキーム+ホスト**により表される概念です。
つまり、URLの最初の`/`より左側と言って良いでしょう。
最近のブラウザだと下の画像のように、Origin部がハイライトされます。
![https://qiita.com/siroganeのうち、httpsとqiita.comがハイライトされている](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/316990/ee4e3ace-344e-eef6-8025-7ba3033395f0.png "Originのハイライト")
厳密に言うとOriginの概念は**ポート番号**を含みますが、通常はスキームによりポートも自動で決まるため無視されます。

理解のために、**同一オリジン(Same Origin)**であるようなURLの例を示します。
以下のURLはすべて`http://example.com`と同一オリジンです。

```
/* http://example.comと同一オリジン */

http://Example.com:80
http://example.com/app/
http://example.com/app/index.html
```

反対に、`http://example.com`と**クロスオリジン(Cross Origin)**の関係にあるようなURLの例を示します。

```
/* http://example.comとクロスオリジン */

http://example2nd.com     // ホストが異なる
http://foo.example.com    // ホストが異なる
http://example.com:8080   // ポートが異なる
https://example.com       // スキームが異なる
```


# Site

初めからテクニカルタームとして定義され利用されてきたOriginと異なり、"サイト(Site)"という言葉は一般に広く利用されていて、かなり曖昧な理解がされています。
[MDN](https://developer.mozilla.org/ja/docs/Glossary/Site)によると、Siteは**登録可能なドメイン(registrable domain)**により決定されるそうです。

...新たな概念が現れました。"登録可能なドメイン"とは何でしょうか？

### 登録可能なドメイン(registrable domain)

登録可能なドメインの最もシンプルな例は、トップレベルドメイン(Top Level Domain, TLD)にラベルを付けたものです。

例えば、`mysite.com`というドメインが取りたいという状況を考えてみます。
この時、お名前.comやGoogle Domainsといった適当なドメイン取得サービスで`mysite.com`が利用可能な状態かを調べ、使えるなら入金してドメインの利用権を得る、といった手続きをとるかと思います。
ドメイン取得サービスを利用しドメインの利用権を"登録"できる、すなわち`mysite.com`は登録可能なドメインといえます。

じゃあURLのうち`A.com`みたいな部分がSiteなのか、と言うとそうでもありません。
**サブドメインが登録可能なドメイン**である場合が存在します。

このような例としてよくあるのが、Github Pagesです。
Github Pagesを公開すると、`[アカウント名].github.io`というドメインが割り当てられます。この`[アカウント名].github.io`、実は
`github.io`のサブドメインであるにもかかわらず、登録可能なドメインとして知られています。
この`github.io`のように、**トップレベルドメインではないものの同等な扱いをされる**ドメイン群を、実際のトップレベルドメインと合わせて**Public Suffix**または**eTLD(effective Top Level Domain)**と呼びます[^1]。
Public Suffixをいくつか例示します。

```
/* Public Suffix(eTLD)の例 */

com
org
co.jp
東京.jp
github.io
netlify.app
```

Public Suffixにラベルを付けたものを**eTLD+1**といいます。
例えば`foo.github.io`はeTLD+1です。

まとめるなら、**Site=登録可能なドメイン=eTLD+1**と言ってよいでしょう。

### 例

以下に挙げるのは、**同一サイト(Same Site)**であるようなURLの例です。

```
/* 同一サイトなURLの組 */

http://example.com/app1/     // eTLD(com)とラベル(example)が一致。
http://example.com/app2/     // 同一オリジンでもある。

http://foo.example.com       // eTLD(com)とラベル(example)が一致。
http://bar.example.com       // 同一オリジンではない。

http://example.com           // eTLD(com)とラベル(example)が一致。
https://example.com          // 同一オリジンではない。
```

反対に、**クロスサイト(Cross Site)**であるようなURLの例も示します。

```
/* クロスサイトなURLの組 */

http://foo.com
http://bar.org               // 何も一致しない。

http://foo.com               // eTLD(com)は一致。
http://bar.com               // +1(foo, bar)が一致しない。

http://foo.github.io         // eTLD(github.io)は一致。
http://bar.github.io         // +1(foo, bar)が一致しない。
```

## OriginとSiteの比較表

ここまでの内容を例でもってまとめます。
先に述べたようにOriginはSiteを包含する概念なので、**"クロスオリジンだが同一サイト"**という状況はありますが、その逆はありません。

| 同一オリジン<br>同一サイト | クロスオリジン<br>同一サイト | クロスオリジン<br>クロスサイト |
|:-:|:-:|:-:|
| `http://example.com/app1/`<br>`http://example.com/app2/` | `http://example.com/app1/`<br>`https://example.com/app2/` | `http://foo.com/app/`<br>`http://bar.com/app/` |
| `http://Example.com:80`<br>`http://example.com`  | `http://foo.example.com`<br>`http://bar.example.com` | `http://foo.github.io`<br>`http://bar.github.io` |

## CSRF対策としてのSameSite cookies

[CSRF](https://developer.mozilla.org/ja/docs/Glossary/CSRF)に対するシンプルかつ効果的な防護策として知られているのが、[SameSite](https://developer.mozilla.org/ja/docs/Web/HTTP/Headers/Set-Cookie/SameSite)属性を`Lax`に設定することです。
これにより、悪意あるサイトから自サイトのCookieを利用することを防げます。
実際、今日のブラウザでは`SameSite=Lax`がデフォルト値となっており、ほとんどのCSRFを防ぐことが出来ているはずです。

しかし、ここで気を付けなければならないのはSameSite属性は**クロスサイトな攻撃には有効だが、クロスオリジンな攻撃には対処できない**ということです。

例えば、`mypaas.com`というドメインでPaaSサービスを展開し、各ユーザーに`mypaas.com`のサブドメインを配布することになったとします。

あるユーザー"Alice"がこのサービスを利用し、`alice.mypaas.com`というドメイン上でセッション情報を利用するアプリケーションを展開しました。
一方で、悪意あるユーザー"Eve"が`eve.com`でサイトを公開し、このドメイン経由で`alice.mypaas.com`に対するCSRF攻撃を試みました。
この攻撃は、`SameSite`属性を`Lax`に設定しておいたおかげで未然に防げました。`eve.com`は`alice.mypaas.com`と異なるSiteなので、`eve.com`経由のリクエストはCookieを送信できないからです。

`eve.com`経由のCSRFに失敗したEveは`eve.mypaas.com`でサイトを公開し、再び`alice.mypaas.com`に対する攻撃を試みました。
`alice.mypaas.com`は**この攻撃を防げません**。
**`eve.mypaas.com`と`alice.mypaas.com`は別オリジンではあるものの、同一サイトだから**です。

このように、サブドメインを利用したCSRF[^2]を想定すると、`SameSite cookies`を設定するだけでは対策として不十分だといえます。
以上の例はPaaSを展開するというかなり特殊な状況ですが、そうでなくとも[Subdomain takeover](https://developer.mozilla.org/ja/docs/Web/Security/Subdomain_takeovers)攻撃などによりサブドメインが信頼できないという状況は考えられます。

### じゃあどうするか

アプリケーション側で**[Originリクエストヘッダー](https://developer.mozilla.org/ja/docs/Web/HTTP/Headers/Origin)**を読み、同一オリジンからのリクエストであることを確認します。

## 参考
- https://developer.mozilla.org/ja/docs/Glossary/Origin
- https://developer.mozilla.org/ja/docs/Glossary/Site
- https://jub0bs.com/posts/2021-01-29-great-samesite-confusion/

[^1]: Public Suffixの一覧はMozillaが管理しており、[https://publicsuffix.org/](https://publicsuffix.org/)で確認できます
[^2]: 厳密にはCORFと呼ぶべきな気もしますが、手法としてはCSRFです