For

2022.7.6

CSRF、同一オリジンポリシー、CORSをまとめて理解する

対象読者

次の文章が100%は理解できないという方が対象です。

フォームに専用のトークンを埋め込むことによるCSRF対策が成り立つのは、ブラウザに同一オリジンポリシーがあるからであり、同一オリジンポリシーがありながら異なるオリジン間でリソースが共有できるのはCORSによるものである。


以降は上記が理解できなかった人(そして理解できなかったかつての自分)に向けての内容です。

CSRFとは?

Cross-Site Request Forgeriesの頭文字をとった言葉で、異なる オリジン 間における偽装リクエストのことです。
CSRFで起きることの例を示します。
CSRF対策をしていないサイトAでは、ログインしている状態で name='title' name='content' の2つのフォームを埋めて送信(POST)することで掲示板に投稿ができるものとします。
CSRFを行う悪意のあるサイトBでは、ユーザーが訪れた瞬間に犯罪予告めいた内容のtitleとcontentでサイトAにPOSTを試みるスクリプトが実行されるものとします。
このとき、サイトBに訪れたユーザーがサイトAにログインしている状態だと、サイトAにCSRF対策がなされていないためにそのまま犯罪予告めいた内容がこのユーザーによるものとして投稿されてしまいます。

上記のようなことを防ぐため、CSRF対策が必要となります。

CSRF対策

具体的な対策としては、サイトAの掲示板投稿フォームに専用のトークンを埋め込むこととなります。
サーバー側でランダムに生成したトークンをサーバー側に保存しておき、そのトークン( name='csrf_token' )をフォームにも埋め込んでおきます。
サーバー側では、掲示板投稿のリクエストを受け付ける際に csrf_token の値を確認し、サーバー側に保存してある値と一致したら正しいリクエストであると判断して処理を進めます。
こうすることで、悪意のあるサイトBからのリクエストにも正しい csrf_token の値が必要になり、その値を知らなければCSRFが受け付けてもらえなくなります。

そこで、サイトBの制作者はサイトAの正しい csrf_token を取得するべく、サイトAの掲示板投稿ページURLにGETリクエストし、レスポンスのHTMLの内容から正しい csrf_token を閲覧しようとしたとします。が、これはブラウザの「同一オリジンポリシー」により失敗します。

同一オリジンポリシー

ブラウザには「同一オリジンポリシー」と呼ばれる制約があります。
同一オリジンポリシーの構成要素として「書き込み」「埋め込み」「読み込み」があり、「埋め込み」についてはHTMLタグ( script, link, img, video, ...etc) による埋め込みは許可するというものなので、ここでは上述の「これはブラウザの『同一オリジンポリシー』により失敗します。」に関わる「書き込み」「読み込み」について言及していきます。
「書き込み」「読み込み」についての制約をまとめると、異なるオリジンからのリクエストは許可するが、レスポンスの閲覧は許可しない、となります。
つまり、上述の「サイトBの制作者はサイトAの正しい csrf_token を取得するべく、サイトAの掲示板投稿ページURLにGETリクエストし、レスポンスのHTMLの内容から正しい csrf_token を閲覧しようとした」という部分について、「サイトAの掲示板投稿ページURLにGETリクエスト」は許可されるが「レスポンスのHTMLの内容から正しい csrf_token を閲覧」は許可されないということになります。
よってサイトBの制作者はサイトAの正しい csrf_token を知ることができず、CSRFができなくなります。

ただ、同一オリジンポリシーだけで考えると、例えばサイトAで「user_idをPOSTするとuser_idに対応したユーザーの投稿リストを取得できる」というAPIを公開した場合、異なるオリジンからそのAPIにuser_idをリクエストしてもレスポンスの投稿リストの閲覧は許可されない、ということになってしまいます。

これを可能にするのがCORSです。

CORSとは?

Cross Origin Resource Sharingのことで、異なるオリジン間でのリソース共有を指します。
これは、HTTPレスポンスで「Access-Control-Allow-Origin」というヘッダーにレスポンス内容の閲覧を許可するオリジンを指定することで、指定されたオリジンではレスポンス内容を閲覧できる仕組みです。
例えば「Access-Control-Allow-Origin」に「*」を指定すると、ワイルドカードなのですべてのオリジンでレスポンス内容の閲覧を許可することができます。
よって、サイトAで「user_idをPOSTするとuser_idに対応したユーザーの投稿リストを取得できる」というAPIのレスポンスには「Access-Control-Allow-Origin」というヘッダーに「*」を指定することでどのオリジンからリクエストがあってもレスポンスの投稿リストの閲覧を許可できます。

最初に戻ると

さて、ここまで来ると冒頭の文章が理解できるようになっているかと思いますのでもう一度見てみましょう。

フォームに専用のトークンを埋め込むことによるCSRF対策が成り立つのは、ブラウザに同一オリジンポリシーがあるからであり、同一オリジンポリシーがありながら異なるオリジン間でリソースが共有できるのはCORSによるものである。


おまけ

私は以下の疑問を抱きました。

GETリクエストによってサイトを閲覧している「ブラウザ」は、ブラウザを起動しているオリジン(すなわち起動しているPCのホスト)からのGETリクエストなのだから異なるオリジンからのGETリクエストとなり、CORSが設定されていないとWEBサイトは閲覧できないのではないか?

これについては、同僚エンジニアが検証して解決してくれました。
あるWEBアプリケーションからのGETリクエストではCORSエラーとなってしまうエンドポイントに対し、ブラウザにエンドポイントのURLを入力してアクセスしたところ、レスポンスを閲覧できました。
つまり、ブラウザはオリジンではないということがわかりました(cURLでも同様でした)。