Cookie

Same Origin Policy(同一オリジンポリシー)
◯同一オリジンの定義
プロトコル、ポート番号、ホストが等しい場合のみ同じオリジンとみなされる。
◯同一オリジンポリシーの制限の対象になるもの/ならないもの
- 制限の対象になる
- JavaScriptでの非同期通信、など
- <script>で読み込まれたJavascript
- <link>で読み込まれたCSS、など
◯制限の挙動
シンプルリクエストに該当する場合
- ブラウザ側でサーバーから取得したリソースへのアクセスが制限される
- どのような場合にシンプルリクエストに該当するかは別途参照
Access to XMLHttpRequest at 'http://localhost:8080/ping' from origin 'http://localhost:3000' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.
シンプルリクエストに該当しない場合
- プリフライリクエストが本来のリクエストの前に実行され、当該リクエストは実行されない
◯同一オリジンポリシーの目的
例えば、ユーザーが悪意のあるwebサイトを訪問する。悪意のあるwebサイトは訪問時に、ユーザーが使用している正規のサイト(もしかしたら認証済)へリクエストを実行する。こうして、そのリクエストから得たリソースを悪意のあるwebサイトは入手することができる。
◯同一オリジンポリシーで防ぐことができない問題
例えば、シンプルリクエストの場合は、正規のwebサーバーのリソースの操作を防ぐことはできない。リソースを入手することはできないが、データベースなどは操作できてしまう。これらの対策として、CSRFの仕組みが必要になる。※後述
プリフライリクエストが伴う場合においても、当該リクエストはキャッシュされるのでCSRFの仕組みは必要と考えられる(Access-Control-Max-Age)。
◯クロスオリジンで通信したい
フロントとバックエンドでそれぞれサーバーを立てており、オリジン(≒ドメイン)が異なる構成は多い。こうした場合は、オリジン間リソース共有 (CORS)というブラウザの仕組みを用いる。後述

オリジン間リソース共有 (CORS)
クロスオリジンにおいて、同一オリジンポリシーの制限を回避するための方法。
シンプルリクエストの場合
リクエスト
Originヘッダーを付与する。これはブラウザが自動で行ってくれる。
Origin: https://foo.example
レスポンス
サーバー側で、Access-Control-Allow-Originヘッダーを付与する。
Access-Control-Allow-Origin: https://foo.example
シンプルリクエストではない場合(プリフライトリクエスト)
本来のリクエストの前にOPTIONSメソッドによるプリフライトリクエストが実行される。
リクエスト
ブラウザが自動で付与する。
Access-Control-Request-Method: POST
Access-Control-Request-Headers: X-PINGOTHER, Content-Type
レスポンス
特筆すべきは、Access-Control-Max-Age
でプリフライトのレスポンスをキャッシュできること。よって、全てのリクエストの度にプリフライトリクエストを実行する必要はない。
Access-Control-Allow-Origin: https://foo.example
Access-Control-Allow-Methods: POST, GET, OPTIONS
Access-Control-Allow-Headers: X-PINGOTHER, Content-Type
Access-Control-Max-Age: 86400

Cookie(資格情報を伴うリクエスト)
Cookieを用いてセッションを管理している場合はさらに知るべきことがある。
シンプルリクエストの場合
リクエスト
クロスオリジンでは、Cookieを送信するために以下の指定が必要。
const invocation = new XMLHttpRequest();
invocation.withCredentials = true;
レスポンス
以下の指定がないと、ブラウザはレスポンスを拒否する。
Access-Control-Allow-Credentials: true
シンプルリクエストではない場合(プリフライトリクエスト)
プリフライトリクエストには資格情報を含めてはならない。
レスポンス
プリフライトリスクエストへのレスポンスに以下を指定する。
Access-Control-Allow-Credentials: true

CSRF攻撃
攻撃手法
罠サイトにフォームを用意しておき、ユーザーがフォームを送信することで、攻撃者の任意のリクエストをユーザーが実行してしまう。
対策
- SameSite Cookie
- クロスオリジンの通信が必要なアプリケーションではNone設定するので利用できない
- CORS
- 前述したようにシンプルリクエストの場合はサーバー側のリソースを操作されてしまうため、完全な対策にはならない
- トークン
- HTMLレンダリング時にhiddenでトークンを埋め込み、リクエスト時に"添付"する
- サーバーがトークンの検証をすることで、同一セッションでのリクエストだと判定できる
- 大概、この手法を取ることが多いという認識
SPA
先のトークンによる対策は、サーバーサイド側で HTMLを生成しないSPAでは使用できない。SPAでは以下の手法が考えられる。
- セッションを確認するような API から Cookie 経由で渡す
- CSRF トークン取得用の API から json として返す
これらが何故CSRFの対策になるのか。罠サイトがAPIを実行してトークンを手に入れた上で、ユーザーにフォームのリクエストを実行させれば同じでことではないのか。しかしこれは以下の条件を満たせば不可能である。
- 正規サイトでのみ、Access-Control-Allow-Credentials: trueをヘッダーに設定する
これにより、罠サイトはAPIを実行してもトークンを取得することはできない。

Cookieの取り扱い
ここまでの議題とは少しずれるが、Cookieの取り扱いについて整理しておく。
Cookie へのアクセス制限
- Secure属性
- HTTPSのリクエストでのみサーバーに送信される
- HttpOnly属性
- JavascriptのDockument.cookieAPIによってアクセスされなくなる
- セッションのCookieには付与するのが推奨
- XSS攻撃を緩和する
Cookie の送信先の定義
- Domain属性
- Cookieを受信することができるホストを指定する
- サーバーがDomainを付与しなかった場合は、サブドメインが除外される
- 裏を返せば、サーバーが付与すればサブドメインにも送信が可能
- Path 属性
- 興味ないので省略
- SameSite 属性
- Strict, Lax, Noneのいずれかの値を設定する
- Noneを設定する場合は、Secure属性の指定も必要である(つまり、HTTPSの必要がある)
マイクロサービスにおけるCookieの扱い
現在、サブドメインで複数のマイクロサービスを扱うアプリケーションを構築している。
(example.com、serviceA.example.com、serviceB.example.com、、、)
以下の要件を満たしたい
- Domain属性がexample.comのCookieをserviceA(or B).example.comに送信したい
- serviceA(or B).example.comのサーバーで、 Domain属性をexample.comに設定してSet-Cookieしたい
結論としては、いずれも可能である。
以下を準拠する。
- 各サーバーは、必ずDomain属性を明示的に付与すること (後方一致)
- SameSiteはNoneに設定する (必然的にHTTPSに限定)