Web API の CSRF 対策とは?

概要

一般的な名称は Cross-Site Request Forgery(クロスサイトリクエストフォージェリ) とされ、他にも CSRF(シーサーフ / シーエスアールエフ) 、 リクエスト強要 、 Session Riding(セッションライディング) 、 XSRF などの呼び方がある

クロスサイトリクエストフォージェリ : Wikipedia

直訳すると サイト横断リクエスト偽造 攻撃といったところだろうか

ここで言う サイト横断 とは、 攻撃対象のWebサイト以外の場所から(発生したHTTPリクエスト) というニュアンスを持っており、例えば以下のような状況を示す

  • 攻撃者が独自に開設したWebサイトの表示
  • 攻撃者から配送された電子メールに記載されたURLリンクのクリック
  • 悪意のない第三者のWebサイト上に配置されたURLリンクのクリック

上記のようなコンテキストにおいて、何らかの方法によって 攻撃対象のWebサイト宛のHTTPリクエストが発生し、意図しない更新操作 を発生させてしまう脆弱性をCSRFと呼ぶ
何らかの方法については、例えば以下が考えられる

  • 悪意のあるWebサイトが表示される際に攻撃者が用意したスクリプトが実行され、APIリクエストやHTTPフォーム送信が発生する
  • ユーザによるURLリンクのクリックにより、WebブラウザからGETのHTTPリクエストが発生する

歴史

高木浩光氏による解説 によると、国内・国外共に初出は2001年頃であるらしい

https://embed.zenn.studio/card#zenn-embedded__55c3a604551ad

近年の動向としては注目度自体は他の脆弱性と比較して高くなく、例えば OWASP(Open Web Application Security Project) が公開している脆弱性のトレンドを示す OWASP Top10 の2021年版においては選外となっている

owasp top 10:2021

https://owasp.org/Top10/ja/ より引用

ちなみに、2013 年版では 8 位にあった ようなので、傾向として発生数が減少しているものと思われる

https://embed.zenn.studio/card#zenn-embedded__cbd39693ffa16

https://owasp.org/www-pdf-archive//OWASP_LA_New_OWASP_Top_10_David_Caissy_2017_07.pdf より引用(p6)

また、IPAが国内で発生した脆弱性についてまとめている ソフトウェア等の脆弱性関連情報に関する届出状況 においても、報告の累計件数や直近の発生件数においても数自体は多くない

https://embed.zenn.studio/card#zenn-embedded__1e6b1404ae5b7

https://www.ipa.go.jp/files/000095630.pdf より引用(p18)

しかしながら、2021年においても EC-CUBE の管理機能において CSRF 脆弱性が発見される など、定期的に脆弱性の報告がなされている状況である

https://embed.zenn.studio/card#zenn-embedded__a3ab64ec27194

攻撃例

IPA(情報処理推進機構) が公開している 安全なウェブサイトの作り方 にて紹介されている図がわかりやすい

https://embed.zenn.studio/card#zenn-embedded__62ed6bbe5a8b3

https://www.ipa.go.jp/security/vuln/websecurity-HTML-1_6.html より引用

攻撃の理解にあたっては、特に以下について注目するとよい

  • 対象のWebサイトのユーザが、正規の手順でログイン済であることを前提とする
    • ログイン済 ≒ ユーザの Web ブラウザにセッション Cookie が発行されている ことがポイント
      • したがって、Webサイトへのユーザーとしてのログイン有無自体は関係がない(後述)
  • 対象のWebサイトが、設計上意図しない更新操作を受け付けてしまうことで攻撃が成立する
    • 受け付けてしまう ≒HTTP リクエストがバックエンドで処理される ことがポイント
      • したがって、HTTPリクエストに対するレスポンスがどのような結果となったかは関係がない
    • Webブラウザに不正なレスポンスが表示されることで発生する攻撃はXSS(Cross-Site Scripting) のように別の攻撃として分類される

日本における著名なCSRF攻撃としては はまちちゃん騒動 が紹介されることが多い

https://embed.zenn.studio/card#zenn-embedded__2f27f1dcc33c6

具体的な攻撃方法については、以下のブログに詳しく説明されていた
攻撃者(はまちや氏)自身で攻撃対象のWebサイト(mixi)に開設していたアカウントの日記機能に悪意のあるWebサイト(はまちや氏の個人サイトの攻撃用ページ)へのリンクが投稿され、
ログイン済の利用者がリンクをクリックすると自身の日記に意図しない投稿がおこなわれてしまう…という脆弱性だったとされる

https://embed.zenn.studio/card#zenn-embedded__424e3a1f090d2

成立条件

対象のWebサイトが 設計上意図しない更新操作を受け付けてしまう ことで攻撃が成立してしまうことは既に述べたが、具体的にどのような実装が問題となりうるかについて、一般的なパターンを確認していく

フォームデータ送信を受け付ける POST エンドポイント

近年のWebアプリケーションは SPA(Single Page Application) と任意のWeb API(RESTなど)を組み合わせてデータの取得や更新をおこなうことが多いが、以前は <form> タグにより送信されたフォームデータをWebサーバーで処理し、テンプレートエンジンを用いてHTMLに値をレンダリングしたものを新たなページとしてブラウザに返却する形式が一般的であった

フォームデータの送信 https://embed.zenn.studio/card#zenn-embedded__d8b1fb6bb00c1

例えば、Webサーバから以下のHTMLが返却されたとする

example.com/index.html
<form action="https://example.com/greeting" method="POST">
  Say: <input name="say" value="Hi"><br />
  To: <input name="to" value="Mom"><br />
  <button>Send my greetings</button>
</form>

これに対して、ブラウザに表示されたフォームの Send my greetings ボタンをクリックすると、以下のようなHTTPリクエストが https://example.com/greeting に向けて送信される

フォームの表示イメージ

HTTPリクエスト

POST /greeting HTTP/2.0
Host: example.com
Content-Type: application/x-www-form-urlencoded
Content-Length: 13

say=Hi&to=Mom

この動作自体は一般的なものであり問題ないが、HTML フォーム送信時の仕様 において、 index.html が example.com 以外のいかなるドメインから配信されていたとしても action で指定されたURLにリクエストが発生する仕様となっており、これがCSRF攻撃を発生させる原因のひとつとなっている

悪用すると、例えば以下のようなHTMLを evil.com で公開した上で、何らかの方法でユーザーに evil.com へアクセスさせることができれば、ユーザーが望まない任意のPOSTリクエストを example.com 宛に送信できる

evil.com/index.html

<form action="https://example.com/greeting" method="POST">
  <input name="say" value="Go away!!!!!">
  <input name="to" value="Dad">
</form>
<script>
  // ただちにフォーム送信を実施
  document.querySelector('form').submit();
</script>

もしも https://example.com/greeting が HTTP Cookie を用いてログイン中のユーザーとして挨拶を投稿するエンドポイントであった場合は、第三者からログイン中のユーザに関連するデータ更新が可能となるため、サービス運営側の観点としてはより危険度が高いと言えるが、
CSRF攻撃の成立条件はあくまで HTTP リクエストが正規の Web サイトから発せられたものか検証せずに受け付けてしまう ことにあるため、 ユーザーのログイン有無自体は直接的な原因とはならない

一方で、Webサイトからの セッション Cookie の発行有無は CSRF 抑止にあたり重要な意味を持つ ことにも留意する。これについては後述する

https://embed.zenn.studio/card#zenn-embedded__4a7f7e7a7d5c6

ログイン機能が無いWebサイトで深刻な被害が発生した一例として、2012年の パソコン遠隔操作事件 では、問い合わせフォームに対してCSRFへの脆弱性を悪用して犯罪予告文を書き込ませる…というものがあった

データの更新操作が発生する GET エンドポイント

前節で説明した <form> タグを用いたフォームデータ送信以外の方法でも、脆弱性を持ったエンドポイントに対してHTTPリクエストを発生させることができれば攻撃は成立させられる

最も一般的な方法として、 https://example.com/greeting?say=Hi&to=Mom に示すようなクエリストリングを付与したURLをWebブラウザやメーラー等でクリックすると、 example.com に対して以下のHTTPリクエストが送信される

HTTPリクエスト
GET /greeting?say=Hi&to=Mom HTTP/2.0
Host: example.com

このケースにおいて、 /greeting エンドポイントがPOSTだけでなくGETによるデータ更新も受け付ける実装になっている場合、 evil.com などで攻撃用HTMLを配信せずともURLを用意してユーザに踏ませるだけで攻撃を成立させることができる

対策については後述するが、POSTエンドポイントと同様に URL ないしリクエストが正規の Web サイトから発行されたものか検証する ことが望ましい

上記以外のケース

節を割いて紹介しなかった一般的なユースケースのひとつとして、SPAからREST APIのエンドポイントに対して更新操作をおこなう場合が考えられる
今まで紹介してきたフォーム送信やURLへのリソース要求は、いずれもWebブラウザにおいてページ書き換えが発生するものだったが、XMLHttpRequest や Fetch API などによりおこなわれるHTTPリクエストは、フォーム送信と比較して以下のような特徴がある

  • リクエスト送信後のページ書き換えがおこなわれない
  • PUT や DELETE 、 PATCH といった任意のHTTPメソッドを発行できる
  • リクエストの内容を柔軟に指定できる…一例として
    • 任意のHTTPヘッダを付加する
    • ペイロードとしてJSONを送信する
  • リクエストの送信可否やレスポンスの読み取り可否について一定の制限が課される
    • Same-Origin Policy を指す。後述

これらのエンドポイントについても、今まで説明してきたのと同様に HTTP リクエストが正規の Web サイトから発行されたものか検証 しないと攻撃が成立する可能性が高い

しかしながら、幾つかのブラウザの挙動やHTTP関連の仕様の影響で攻撃が抑止される場合があり、それらも加味した実装を対策として紹介している記事も多いが、今回はCSRF攻撃への根本的な対策方法について検討していく

関連する仕様

攻撃の抑止方法の詳細に踏み込む前に、実行環境にあたるWebブラウザの挙動に関連する幾つかの仕様について確認しておく
脆弱性に複数の対策を織り込むことは多層防御 の観点からも望ましい

https://embed.zenn.studio/card#zenn-embedded__13630d28b2ed5

フォームデータ送信

成立条件 の節ではHTMLフォームからのPOST通信について説明したが、 <form> タグは特定の属性を付与することで以下の形式のHTTPリクエストを発行することができる

https://embed.zenn.studio/card#zenn-embedded__2f0c6b0add151

  • HTTPメソッド(methodにて指定)
    • POST
    • GET
  • コンテンツのMIME タイプ
    • application/x-www-form-urlencoded
    • multipart/form-data
    • text/plain

注意すべき点として、例えば text/plain のMIMEタイプを利用して以下のようなフォームを作成すると、JSONとしてパース可能なペイロードを送信することができる

本来であれば、 Content-Type: application/json の場合に限ってリクエストの内容をJSONとして解釈するのが望ましいが、利用しているWebフレームワークの仕様や設定について確認することを推奨する

index.html

<form
  action="https://example.com/api/greeting"
  method="POST"
  enctype="text/plain"
>
  <input name='{"say": "Go away!!!!!", "to": "Dad", "trash": "' value='"}' />
  <button>Attack</button>
</form>
HTTPリクエストのペイロード
{"say": "Go away!!!!!", "to": "Dad", "trash": "="}

なお、次節で説明する同一オリジンポリシーの文脈において、HTMLフォームから送信できる範囲のHTTPリクエスト(に類似したもの)を単純リクエスト(Simple request) として定義している
これは、 HTMLフォームから送信したHTTPリクエストはプリフライトリクエストを発生させない ということを意味する

この挙動は、同一オリジンポリシーの誕生より前にHTMLフォームが存在していたことから、後方互換性を壊さないために規定されているものらしい

同一オリジンポリシー

Webの世界においては、Webブラウザから複数ドメインに対して通信が発生するケースにおいて、いかにコンテンツの安全性を担保するか…といったことが古くから課題となっており、その文脈において 同一オリジンポリシー(Same-Origin Policy / SOP / 同一生成元ポリシー) という考え方が浸透している

ページの プロトコル、ポート番号、ホスト名が同一 の場合を同一オリジン、値が異なる場合はクロスオリジンと定義し、リクエストの送信やレスポンスの取得に一定の制限を課すものである

CORSについて、特にCSRFの文脈においては、プリフライトリクエスト(Preflight request)について理解しておくとよい

https://embed.zenn.studio/card#zenn-embedded__1357467003cd2

プリフライトリクエストは、特にWebアプリケーションをSPAとバックエンドのREST APIにて構成している場合において、API側でクロスオリジンからのリクエスト可否を決定するための機構である

HTTPリクエストが クロスオリジン、かつ前述の単純リクエストに当てはまらない 時に、ブラウザ側でエンドポイントに対してOPTIONSメソッドを発行し、バックエンドにて生成されたレスポンスのHTTPヘッダの内容に応じてメインリクエストを送信するかどうか決定する

https://developer.mozilla.org/ja/docs/Web/HTTP/CORS より引用

一連の動作はWebブラウザにてアプリケーションコードから見て透過的に実施されるため、結果として以下に挙げたような 単純リクエストに当てはまらない HTTP リクエストでは、通常 CSRF 攻撃は成立しない と言える

  • PUT / PATCH / DELETEといったHTTPメソッドを用いたリクエスト
  • Content-Type: application/json ヘッダが指定された場合のみ処理を受け付けるエンドポイントへのリクエスト
  • X-Requested-With: XMLHttpRequest ヘッダなど、特定の固定ヘッダが設定されている場合のみ処理を受け付けるエンドポイントへのリクエスト

もっとも、この挙動はクロスドメイン通信を許可する設定がバックエンド側にておこなわれていない(Access-Control-Allow-Origin: * などのヘッダを返却しない)ことが前提となるため、根本的な対策としては後述する手法を選択するのが望ましいと思われる

もう一点注意すべきこととしては、単純リクエストとしてプリフライトリクエストが発生せず送信されたHTTPリクエストの場合、 CORS の制限によりレスポンスの読み取りが失敗したとしても CSRF 攻撃は成立しうる 可能性が高い

プリフライトリクエストの段階で失敗した例(図上段)と、プリフライトリクエスト通過後レスポンス読み取りで失敗した例(図下段)

プリフライトリクエストが発生し、かつ失敗した場合のメッセージは以下(太字は筆者が注記)
この場合はOPTIONSリクエストまでしか発生しておらず、 CSRF を成立させるための本体のリクエストは発生していない 状況といえる

Access to fetch at ‘…..’ from origin ‘…..’ has been blocked by CORS policy: Response to preflight request doesn’t pass access control check: 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.

プリフライトリクエストを通過し、ブラウザ上でのレスポンスの読み取りの段階でエラーとなった場合のメッセージは以下となる
この場合は実際にリクエストが発生してしまっているため、ブラウザでレスポンスが読み取れるかどうかに関わらず攻撃は成立している 可能性が高い

Access to fetch at ‘…..’ from origin ‘…..’ 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.

なお、プリフライトリクエストの仕様はWebブラウザに固有のもので、例えばCLI上での curl コマンドや、Pythonにおける Requests モジュールの利用など、Webブラウザ以外の環境から発生するリクエストについては基本的に制約は課されない

近年、CSRFの緩和を目的として導入された仕様として HTTP Cookie の SameSite 属性がある

https://embed.zenn.studio/card#zenn-embedded__2fd4bfac061b2

これは、Webサイトより Set-Cookie ヘッダでCookie付与をおこなう際に、以下の属性を追加で指定することでクロスサイト(歴史的経緯よりクロスオリジンではないことに注意)での通信においてアクセス先にCookieが付与されなくなる

  • SameSite=Lax の場合
    • トップレベルナビゲーション(Webブラウザ上のURL遷移)、かつHTTPメソッドが GET / HEAD / OPTIONS / TRACE のいずれかである場合のみCookieが送信される
  • SameSite=Strict の場合
    • いかなる場合もCookieが付与されない
  • SameSite=None の場合
    • いかなる場合もCookieが付与される

一般的なWebサイトにおいて、従来の動作を維持しながらCSRF攻撃への対策をするには、 セッション Cookie に SameSite=Lax を指定する のが望ましい

この挙動は、外部サイトからAタグ等によりURL遷移でサイトを訪れた際にはリクエストにCookieが付与される一方で、CSRF攻撃のリスクとなり得る別サイトからのPOSTリクエストにはセッションCookieが付与されず、ログインが必要なエンドポイントに対するアクセス全般が不成立となる

ただし、前項で説明したとおりCSRF攻撃は ユーザーのログイン有無自体は直接的な原因とはならず、HTTP リクエストが正規の Web サイトから発行されたものか検証していない場合に成立する(先に説明したパソコン遠隔操作事件の事例を参照)ため、後述する根本対応の実施をおこなうことが望ましい

抑止のための設計・実装

CSRFを抑止するために必要となるアプリケーション設計については、OWASP Cheat Sheet Seriesで詳しく紹介されている

https://embed.zenn.studio/card#zenn-embedded__f24dbb4476ba7

本記事では、中でもToken Based Mitigation として紹介されている2種類の方法について解説する

Synchronizer Token Pattern

今まで説明してきた通り、CSRF攻撃の抑止には HTTP リクエストが正規の Web サイトから発行されたものか検証する 必要がある

例えば https://www.abcbank.com という銀行のWebサイトがあったとして、 /transferfund というエンドポイントへのGETリクエストでHTMLフォームを返却し、/transfer エンドポイントへのPOSTリクエストで挨拶を投稿するWebサイトを考えた時、
CSRF攻撃とは正規ユーザーであれば必ず経由するはずの /transferfund を迂回し、直接 /transfer へリクエストを発行する…という性質がある

すなわち、 /transferfund エンドポイントにアクセスした際に、 ユーザーに対してセッション Cookie を割り当てた上で、CSRF 検証用のトークンを発行して HTML レスポンスで返却し、以降の HTTP リクエストに必ずトークンを含めた上でサーバーサイドで一致することを確認 すれば、リクエストが正規のWebサイトから発行されたものであることが検証できる

https://embed.zenn.studio/card#zenn-embedded__7c5819050bb25

攻撃者は、攻撃対象のユーザーが /transferfund エンドポイントにアクセスした際に手に入れた トークンの内容を知ることができない(攻撃者自身がサイトにアクセスした場合は攻撃対象のユーザーとは別のセッションIDとトークンが返却される)ため、CSRF攻撃を成立させることができない

処理フローとしては以下のようになる

https://faun.pub/cross-site-request-forgery-protection-using-synchronizer-token-pattern-72b246ded56c より引用

実装にあたっては以下に留意する

  • 利用しているWebフレームワークが提供するCSRF保護機構が存在する場合、独自実装はせずそちらを利用したほうがよい
    • 上記の仕組みを自ら実装する場合、トークンを暗号論的に安全な方法で生成したり、トークンの比較をタイミング攻撃に対して一定の強度を持つ方法でおこなう必要があり、新たな脆弱性を埋め込むリスクが高く推奨されない
  • CSRF検証用トークンをWebブラウザからCookie経由で送信しない
    • OWASPの該当ページにて CSRF tokens should not be transmitted using cookies. と言及されている
    • 送信にCookieを利用してしまうとセッションCookieと同じライフサイクルでトークンが送信されてしまい検証にならないからと思われるが、SameSite=Strict が指定されていれば問題ないような気もするが…?

REST APIにおける対処についても同様の手法でよく、クライアントサイドのJavaScriptにてHTMLに設定されたトークンを取得し、HTTPリクエストヘッダやペイロードに設定してサーバーサイドまで送信すればよい

より詳細な実装については、利用しているWebアプリケーションフレームワークのドキュメントを参照すべきだが、この時そのフレームワークが採用している手法が Synchronizer Token Pattern と Double Submit Cookie Pattern のどちらであるかについては意識する必要がある

https://embed.zenn.studio/card#zenn-embedded__2e7f7740d5242

ちなみに、本手法についてCSRFトークンをセッション単位でなくHTTPリクエスト単位にローテーションする設計にすると、フォームの二重送信(フォームのSubmitボタン連打や、複数タブにて連続してSubmitをおこなった際に処理が多重に呼び出せてしまう問題)を抑止する機能としても活用できる

https://embed.zenn.studio/card#zenn-embedded__aab7b5f121e55

NTTデータが開発しているTERASOLUNAにおいてはトランザクショントークンチェック という名前付けがされていた

https://embed.zenn.studio/card#zenn-embedded__1b3ba93fe6538

ドキュメントでは基本的にSynchronizer Token Patternの利用を推奨しているが、これはCSRF検証用トークンをセッションCookieに紐付けてサーバサイドにて管理する必要がある

この管理をおこなわずにCSRF対策をおこないたい場合にはDouble Submit Cookie Pattern が利用できる

本パターンでは、CSRF 検証トークンを Cookie に設定して Web ブラウザに返送および管理を委譲した上で、リクエスト時にCookie以外の配送経路(HTTPリクエストヘッダやペイロード)でトークンを取得し、サーバサイドでHTTP リクエスト上のトークン値と Cookie 上のトークン値の比較をおこない、リクエストが正規サイトから送信されたものであることを確認する

https://embed.zenn.studio/card#zenn-embedded__5612e9cf3d5b8

処理フローとしては以下のようになる

https://medium.com/@kaviru.mihisara/double-submit-cookie-pattern-820fc97e51f2 より引用

Synchronizer Token Patternではセッションで管理していたCSRFトークンをWebブラウザ上のCookieに保存していることから一見危険性が高いように感じられるが、なぜこの実装で問題ないかについて少し考えたい

CSRF検証トークンが持つべき性質として、以前の説明から以下があることがわかっている

  1. 攻撃者が知り得ない・推測できない値であること
  2. セッションIDに紐づく値であること
  3. クロスサイトからのアクセス時にバックエンドに送信されないこと

1については、ユーザーのセッションCookieはCSRFとは別の脆弱性が無い限りには攻撃者は知ることができず、
2についてはセッションCookieと同様のライフサイクルで生成および送受信されるため問題がない
3に関しても、比較する片方の値はCookieにより配送されているが、もう片方の値をそれ以外の経路にて送信していることから検証をおこなうことができ、結果Synchronizer Token Patternと同等の仕様が満たせていることがわかる

注意点としては、Cookie は状況によって上書きすることができるため、攻撃者が何かしらの手法でユーザーに対して任意のCSRF検証トークンのCookieを強制できれば攻撃を成立させられてしまう

一例として、以下の記事で示されているように通信経路上に攻撃者が介在しているケースにおいては、攻撃者から任意のCookie値を強制されることからサイトを保護することはできない
Double Submit Cookieのアーキテクチャは通信内容が改ざんされないことを前提としているため、Synchronizer Token Patternと比較した際に固有の攻撃リスクが存在する

https://embed.zenn.studio/card#zenn-embedded__d4492332c08a8

これについてはドキュメントにて対策が書かれており、サーバーサイドでのCSRFトークン発行時にユーザーのコンテキスト固有の値を含めて暗号化して配送した上で、Webブラウザから送られてきたトークンを復号化してから比較すれば、改ざんを検知することができる
こうすると、攻撃者が任意の検証トークン値を強制できたとしても、復号化後にユーザーのコンテキスト固有の値とマッチせず、攻撃を抑止することができる

To enhance the security of this solution include the token in an encrypted cookie – other than the authentication cookie (since they are often shared within subdomains) – and then at the server side match it (after decrypting the encrypted cookie) with the token in hidden form field or parameter/header for AJAX calls. This works because a sub domain has no way to over-write an properly crafted encrypted cookie without the necessary information such as encryption key.

https://embed.zenn.studio/card#zenn-embedded__a7b2fe4bd2eb5

例えば、DjangoではデフォルトではDouble Submit CookieパターンをベースにしたCSRF保護がおこなわれているが、前述した問題を考慮した実装がおこなわれている

https://embed.zenn.studio/card#zenn-embedded__16bbe0ca8008c

https://embed.zenn.studio/card#zenn-embedded__52d78bc7bd9b8

その他の対策方法

以下の節で記載があるので興味があれば見てみるとよいが、基本的に前述のいずれかの対策を取ることが推奨される

  • CookieのSameSite属性の利用
  • Originヘッダとサーバサイドのオリジンの比較
  • カスタムリクエストヘッダのチェック
  • リクエスト前にユーザー操作を実施させる

https://embed.zenn.studio/card#zenn-embedded__4a6a3a1c0ce66

細かなケースへの対処

対策への考え方がわかったところで、より現実的なケースについて考察する

SPA を静的コンテンツとして配信している場合

最近のWebアプリケーションでよくある構成として、フロントエンドのアプリケーションをSPAとして作成した上で静的サイトとして配信しており、ページの初期ロード時のHTMLにCSRF検証トークンが存在しないケースが考えられる

これについては、ページの初期表示時にログイン状態の取得をおこなうエンドポイント等のHTTPレスポンス内でCSRFトークンを受け取って以降のリクエストで利用すればよい

また、Ajax通信中に発行されたCookieは以降のリクエストにおいては自動的に送信されるため、Double Submit Cookie Patternを利用している場合はリクエストの度に document.cookie の内容を確認し、トークンを送信すればよい

https://embed.zenn.studio/card#zenn-embedded__f601a1dd78389

Web サイトへのログイン前の場合

前項までに紹介してきたSameSite属性の登場やブラウザのデフォルト挙動の変化により、現在のCSRF攻撃はログイン後(≒ セッションCookie発行後)については成立させるのが難しくなってきている

むしろ、相対的にログインが必須でないページの方がCSRF攻撃のリスクが高まっている状況だが、これについてはプリセッション(認証を完了する前に事前発行するセッションCookie)を発行すべきだとしている
Djangoでは 匿名セッション としてデフォルトで有効化されているなど、Webフレームワーク側で予め考慮されているケースも多い

https://embed.zenn.studio/card#zenn-embedded__db6bacce00d2

例えば Double Submit Cookie Pattern の項で紹介したフローでは /login へのPOSTが成功したタイミングでセッションCookieとCSRFトークンを発行しているが、これは /login ページのGETのタイミングでおこなったほうがCSRF抑止の観点では望ましい

ただその場合はセッション ID 固定化攻撃を避けるため、ログイン時にセッションIDの再発行を実施すべきであるとされる

https://medium.com/@kaviru.mihisara/double-submit-cookie-pattern-820fc97e51f2 より引用

ログインの概念がない問い合わせページのCSRF対策としては、CAPTCHAに代表されるようなユーザーがWebブラウザ上で特定の操作をおこなったことをサーバサイドで検証できるような仕組みを導入することで、リスクを緩和できる

https://embed.zenn.studio/card#zenn-embedded__9ae93f6f0649f

https://embed.zenn.studio/card#zenn-embedded__34f108eae7775

副作用のある GET エンドポイントの場合

GETエンドポイントに対してバックエンドでの更新操作をおこなわないのが原則となる
ただし、メールアドレスのアクティベーション や ログアウト といった処理フローにおいて、ユーザーの利便性からGETエンドポイントでの更新操作を選択したくなるケースは存在するため、実装する場合はリスクについて留意する必要がある

メールアドレスのアクティベーションにおいては、一定時間で期限切れになる、第三者が推測不可能な認証コードを生成した後でURLの一部に設定し、リクエスト受付時に値のチェックをおこなうとよい

https://embed.zenn.studio/card#zenn-embedded__56b2bff155bee

なお、これはCSRF攻撃とは関係ないが、一部のブラウザやメールクライアントが処理速度改善を目的としてURL のプリフェッチをおこなう場合があるため、実装にあたってはURLアクセス時に直接バックエンドにリクエストが飛ぶようにはせず、JavaScript等を用いてページの初期ロード時に目的のエンドポイントへリクエストをおこなう設計にするとよい

この時エンドポイントをPOSTにするとCSRFトークンの検証をおこなうことができるのでより安全に実装できる

https://embed.zenn.studio/card#zenn-embedded__3e024847fb163

ログアウトについては、一般的に /logout などのエンドポイントにGETでアクセスした際にそのままログアウトがおこなわれる場合が多いが、POSTを受け付けるエンドポイントを別途作成し、そちらでCSRFトークンをチェックするように変更するとよい

https://embed.zenn.studio/card#zenn-embedded__68ccbed5a7e06

JWT による認証をおこなっている場合

JWTを利用してセッション管理をおこなっているアプリケーションの場合、 Authorization HTTPヘッダ等のCookie以外の配送経路を用いている場合はCSRF脆弱性を抑止することができる

以下の記事に詳しい

https://embed.zenn.studio/card#zenn-embedded__398c4ade852e6