🏄

CSRF(Cross-Site Request Forgery)攻撃について

2022/10/30に公開
2

ふと気になって調べたことの備忘メモです ✍

(2022/11/3追記)ご指摘頂いた内容を踏まえて加筆修正をおこないました

なぜ調べたか

Webアプリケーションの開発に携わっていると CSRF という脆弱性への対処を求められますが、多くの場合利用しているフレームワークが設定追加だけで対応してくれたり、既に前任者によって適切な処置がされていたりなど、実務上で目を向ける機会はその重要性と比較して少ないのでないかと思います

また、Webブラウザの実装やHTTP周辺の関連仕様の変化から陳腐化している情報も多く、現代において全体感と具体的な対処法を理解するには少しばかりハードルが高いように感じていました

ですので、自身の現時点での認識を明文化して残しておくことにしました
なお、私はWebセキュリティの専門家でなく、一介の開発者のため、誤りが多分に含まれる可能性があります
ご指摘を頂ければ修正したいと思います🙏

調べたこと

概要

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

https://ja.wikipedia.org/wiki/クロスサイトリクエストフォージェリ

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

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

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

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

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

歴史

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

https://www.ipa.go.jp/security/vuln/event/documents/20060228_3.pdf

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

https://owasp.org/Top10/ja/


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

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

https://owasp.org/www-pdf-archive//OWASP_LA_New_OWASP_Top_10_David_Caissy_2017_07.pdf


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

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

https://www.ipa.go.jp/security/vuln/report/vuln2021q4.html


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

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

https://www.ec-cube.net/info/weakness/20211111/

攻撃例

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

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


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://ascii.jp/elem/000/000/063/63560/

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

https://mohritaroh.hateblo.jp/entry/20050420/1113967566

成立条件

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

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

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

https://developer.mozilla.org/ja/docs/Learn/Forms/Sending_and_retrieving_form_data

例えば、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.htmlexample.com 以外のいかなるドメインから配信されていたとしても action で指定されたURLにリクエストが発生する仕様となっており、これがCSRF攻撃を発生させる原因のひとつとなっている

https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#form-submission-algorithm

この挙動は現代におけるユースケースから考えると望ましい挙動ではないが、Webに関する仕様の多くが後方互換性を強く意識して改訂されてきている経緯から維持されていると思われる

https://standards.mitsue.co.jp/archives/001233.html

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

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

https://developer.mozilla.org/ja/docs/Web/HTTP/Cookies

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

https://ja.wikipedia.org/wiki/パソコン遠隔操作事件

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

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

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

https://developer.mozilla.org/ja/docs/Web/HTTP/Basics_of_HTTP/Identifying_resources_on_the_Web#query

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ブラウザにおいてページ書き換えが発生するものだったが、XMLHttpRequestFetch API などによりおこなわれるHTTPリクエストは、フォーム送信と比較して以下のような特徴がある

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

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

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

関連する仕様

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

https://e-words.jp/w/多層防御.html

フォームデータ送信

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

https://developer.mozilla.org/ja/docs/Web/HTML/Element/form

  • 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リクエストはプリフライトリクエストを発生させない ということを意味する

https://developer.mozilla.org/ja/docs/Web/HTTP/CORS#単純リクエスト

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

https://zenn.dev/qnighy/articles/6ff23c47018380#preflightが不要なケース

同一オリジンポリシー

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

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

https://developer.mozilla.org/ja/docs/Web/Security/Same-origin_policy

この制限を CORS(Cross-Origin Resource Sharing / オリジン間リソース共有) と呼ぶ

https://developer.mozilla.org/ja/docs/Web/HTTP/CORS

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

https://developer.mozilla.org/ja/docs/Glossary/Preflight_request

プリフライトリクエストは、特に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://developer.mozilla.org/ja/docs/Web/HTTP/Headers/Set-Cookie/SameSite

これは、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 サイトから発行されたものか検証していない場合に成立する(先に説明したパソコン遠隔操作事件の事例を参照)ため、後述する根本対応の実施をおこなうことが望ましい

https://ja.wikipedia.org/wiki/パソコン遠隔操作事件

ちなみに、2022年現在の状況として、一部ブラウザでSameSite属性が未指定の場合のデフォルトの挙動がLaxに変更されているらしく、対策をおこなっていない既存サイトの安全性が向上しつつある
その他にも 2分間ルール といった複雑な挙動があり、詳細は以下に詳しい

https://blog.tokumaru.org/2022/01/impact-conditions-for-no-CSRF-protection-sites.html

SameSite Cookieに関する解説は以下に詳しい

https://blog.jxck.io/entries/2018-10-26/same-site-cookie.html

抑止のための設計・実装

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

https://cheatsheetseries.owasp.org/cheatsheets/Cross-Site_Request_Forgery_Prevention_Cheat_Sheet.html

本記事では、中でも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://cheatsheetseries.owasp.org/cheatsheets/Cross-Site_Request_Forgery_Prevention_Cheat_Sheet.html#synchronizer-token-pattern

攻撃者は、攻撃対象のユーザーが /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 PatternDouble Submit Cookie Pattern のどちらであるかについては意識する必要がある

https://cheatsheetseries.owasp.org/cheatsheets/Cross-Site_Request_Forgery_Prevention_Cheat_Sheet.html#javascript-guidance-for-auto-inclusion-of-csrf-tokens-as-an-ajax-request-header

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

https://hiroga.hatenablog.com/entry/2017/10/04/080137

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

https://terasolunaorg.github.io/guideline/public_review/ArchitectureInDetail/DoubleSubmitProtection.html#doublesubmit-how-to-use-transaction-token-check

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

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

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

https://cheatsheetseries.owasp.org/cheatsheets/Cross-Site_Request_Forgery_Prevention_Cheat_Sheet.html#double-submit-cookie

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


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://blog.tokumaru.org/2013/09/cookie-manipulation-is-possible-even-on-ssl.html

これについてはドキュメントにて対策が書かれており、サーバーサイドでの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://cheatsheetseries.owasp.org/cheatsheets/Cross-Site_Request_Forgery_Prevention_Cheat_Sheet.html#double-submit-cookie

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

https://docs.djangoproject.com/en/4.1/ref/csrf/#frequently-asked-questions

https://ops.jig-saw.com/tech-cate/django-react

その他の対策方法

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

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

https://cheatsheetseries.owasp.org/cheatsheets/Cross-Site_Request_Forgery_Prevention_Cheat_Sheet.html#defense-in-depth-techniques

細かなケースへの対処

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

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

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

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

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

https://computer-technology.hateblo.jp/entry/20131226/p1

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

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

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

https://cheatsheetseries.owasp.org/cheatsheets/Cross-Site_Request_Forgery_Prevention_Cheat_Sheet.html#login-csrf

例えば 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://ja.wikipedia.org/wiki/CAPTCHA

https://turningp.jp/network_and_security/public_form-csrf

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

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

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

https://colo-ri.jp/develop/2012/07/web-how-to-mail-activation.html

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

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

https://developer.mozilla.org/ja/docs/Web/HTTP/Link_prefetching_FAQ

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

https://webcache.googleusercontent.com/search?q=cache:SQpwgKa4MqoJ:https://www.websec-room.com/2017/01/07/2727&cd=1&hl=ja&ct=clnk&gl=jp

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

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

以下の記事に詳しい

https://qiita.com/mejileben/items/ae7b346f0fc4a59101b5

まとめ

まとめです

  • ブラウザの挙動は時代によって変化するので、より根本的な対処となる Synchronizer Token Pattern または Double Submit Cookie Pattern を導入しよう 🐂
  • 自分で実装せずWebアプリケーションが提供する機能を利用しよう 🌴
  • 未ログインのユーザーにとっても発生しうる脆弱性であることを理解しよう 👺

参考資料

記事中で触れられなかったものの参考にした記事です 🙏

https://developer.mozilla.org/en-US/docs/Web/Security/Types_of_attacks#cross-site_request_forgery_csrf

https://utkusen.medium.com/the-state-of-csrf-vulnerability-in-2022-3858e6d90ab9

https://www.ipa.go.jp/security/vuln/websecurity.html

https://qiita.com/mpyw/items/0595f07736cfa5b1f50c

https://numb86-tech.hatenablog.com/entry/2019/02/13/221458

https://stackoverflow.com/questions/48002861/whats-the-relationship-between-csrfmiddlewaretoken-and-csrftoken

https://blog.tokumaru.org/2018/11/csrf_26.html

Discussion

ockeghemockeghem

Cross-Site Request Forgery Prevention Cheat Sheetは読むたびに内容が変更されているように思います。内容が安定しておらず、信頼性が低いと考えられます。
Double Submit Cookieについては、以前以下の記事で内容を批判しました。

https://blog.tokumaru.org/2018/11/csrf_26.html

これを読んでいただけるとわかると思いますが、Double Submit Cookieについては推進派と反対派が論争をしていて編集合戦の様相を呈しています。私は反対派です。

Cookieの改変については以下の記事および動画で解説をしています。

https://blog.tokumaru.org/2013/09/cookie-manipulation-is-possible-even-on-ssl.html

https://www.youtube.com/watch?v=GP1eEit1quY

サーバーサイドでのトークン発行時に値を暗号化して配送した上で、Webブラウザから送られてきたトークンを復号化してから比較すれば、改ざんを検知することができる

これは文字通り解釈すると間違いでしょう。攻撃者は自分用のトークンCookieを入手して、それを被害者のブラウザにセットしてやれば改ざんすることなく悪用が可能です。トークンにユーザIDなどを含める等しなければ、CSRF攻撃は検知できないと思います。

JWTはセッションCookieと同等の役割をする上、仕様上改ざん検知をおこなうことができるため、Cookieを用いたCSRFチェック全般が不要になる

JWTをCookieで受け渡しするサイトもあり、この場合はCSRF脆弱になる可能性があります。JWTだからCSRFセーフというわけではなく、トークンをリクエストヘッダで送信するからCSRF脆弱性の余地がないだけで、改ざん検知は関係ないと思います。

Yuuki TakahashiYuuki Takahashi

ご指摘および詳細に解説を頂きましてありがとうございます🙏
数日中に以下の方向で加筆・修正したいと思います

Double Submit Cookieについて

解答:CSRFの防止策に関するチートシートにツッコミを入れる はじめ頂いたリンクについて拝読致しました
OWASPのDouble Submit Cookieの説明を読んで漠然と不安を感じていたのですが、危険性のある具体的なケースを例示頂けたことでイメージが湧き大変ありがたかったです

一方で、(徳丸さんの他の記事でも紹介されていますように、)いくつかの著名なWebフレームワークでも現在採用されている手法であることと、本記事はアプリケーション開発者に向けた記事であり、利用しているWebフレームワークが採用している実装をそのまま利用することを推奨する趣旨であることから、Double Submit Cookie を使ってはならない…といった立場は取らないつもりで考えています

つきましては、以下について補足したいと思います

  • 注意点としては、Cookie は状況によって上書きすることができるため、攻撃者が何かしらの手法でユーザーに対して~何かしらの手法 について HTTPSを使ってもCookieの改変は防げないことを実験で試してみた の記事のケースを紹介し、その場合において Double Submit Cookie は Synchronizer Token Pattern と比較した際に固有の攻撃リスクがある
    • が、一般的にはWebアプリケーションフレームワーク側でそうした問題への考慮がおこなわれているため、利用者としてはまずは各フレームワークのドキュメントを読んで適切な実装をおこなうのが重要である
  • Webサイトが他の脆弱性を含む際にリスクが増加する点については、HTTPヘッダインジェクションなどDouble Submit Cookie特有のリスクとなり得るものについて紹介する
  • クッキーモンスターバグについては近年のブラウザ環境では解消されているため記事中では紹介しない

こちらについては、ご指摘頂いている トークンにユーザIDなどを含める等しなければ のニュアンスが漏れてしまっていることが問題であり、その旨を加筆する必要があると理解しました
検証用トークンについて、Cookieで配送する以上は外部から特定値を強制されるリスクがあるため、そのユーザーのコンテキスト(≒セッション)固有に発行されたものであることが復号化時に判断可能であることが必須要件である…という記載が必要であると考えています

JWTについて

私がJWTについて本番環境での利用経験がなく、特に認識が曖昧なまま書いてしまっていたものと思います
JWTをCSRF対策に活用できるのは配送経路にHTTPヘッダ等を利用した(Cookieを用いなかった)場合に限られることと、改ざん検知の下りは記載を削除します
(これについては、そもそも参考として貼り付けていた記事自体にもそのような注意喚起がされていたものでしたので、杜撰な記述をしてしまっていました…今後気をつけたいと思います)