CSRF(Cross-Site Request Forgery)攻撃について
ふと気になって調べたことの備忘メモです ✍
(2022/11/3追記)ご指摘頂いた内容を踏まえて加筆修正をおこないました
なぜ調べたか
Webアプリケーションの開発に携わっていると CSRF という脆弱性への対処を求められますが、多くの場合利用しているフレームワークが設定追加だけで対応してくれたり、既に前任者によって適切な処置がされていたりなど、実務上で目を向ける機会はその重要性と比較して少ないのでないかと思います
また、Webブラウザの実装やHTTP周辺の関連仕様の変化から陳腐化している情報も多く、現代において全体感と具体的な対処法を理解するには少しばかりハードルが高いように感じていました
ですので、自身の現時点での認識を明文化して残しておくことにしました
なお、私はWebセキュリティの専門家でなく、一介の開発者のため、誤りが多分に含まれる可能性があります
ご指摘を頂ければ修正したいと思います🙏
調べたこと
概要
一般的な名称は Cross-Site Request Forgery(クロスサイトリクエストフォージェリ)
とされ、他にも CSRF(シーサーフ / シーエスアールエフ)
、 リクエスト強要
、 Session Riding(セッションライディング)
、 XSRF
などの呼び方がある
直訳すると サイト横断リクエスト偽造
攻撃といったところだろうか
ここで言う サイト横断
とは、 攻撃対象のWebサイト以外の場所から(発生したHTTPリクエスト)
というニュアンスを持っており、例えば以下のような状況を示す
- 攻撃者が独自に開設したWebサイトの表示
- 攻撃者から配送された電子メールに記載されたURLリンクのクリック
- 悪意のない第三者のWebサイト上に配置されたURLリンクのクリック
上記のようなコンテキストにおいて、何らかの方法によって 攻撃対象のWebサイト宛のHTTPリクエストが発生し、意図しない更新操作
を発生させてしまう脆弱性をCSRFと呼ぶ
何らかの方法については、例えば以下が考えられる
- 悪意のあるWebサイトが表示される際に攻撃者が用意したスクリプトが実行され、APIリクエストやHTTPフォーム送信が発生する
- ユーザによるURLリンクのクリックにより、WebブラウザからGETのHTTPリクエストが発生する
歴史
高木浩光氏による解説 によると、国内・国外共に初出は2001年頃であるらしい
近年の動向としては注目度自体は他の脆弱性と比較して高くなく、例えば OWASP(Open Web Application Security Project)
が公開している脆弱性のトレンドを示す OWASP Top10 の2021年版においては選外となっている
https://owasp.org/Top10/ja/ より引用
ちなみに、2013 年版では 8 位にあった ようなので、傾向として発生数が減少しているものと思われる
https://owasp.org/www-pdf-archive//OWASP_LA_New_OWASP_Top_10_David_Caissy_2017_07.pdf より引用(p6)
また、IPAが国内で発生した脆弱性についてまとめている ソフトウェア等の脆弱性関連情報に関する届出状況 においても、報告の累計件数や直近の発生件数においても数自体は多くない
https://www.ipa.go.jp/files/000095630.pdf より引用(p18)
しかしながら、2021年においても EC-CUBE の管理機能において CSRF 脆弱性が発見される など、定期的に脆弱性の報告がなされている状況である
攻撃例
IPA(情報処理推進機構)
が公開している 安全なウェブサイトの作り方 にて紹介されている図がわかりやすい
https://www.ipa.go.jp/security/vuln/websecurity-HTML-1_6.html より引用
攻撃の理解にあたっては、特に以下について注目するとよい
- 対象のWebサイトのユーザが、正規の手順でログイン済であることを前提とする
-
ログイン済 ≒ ユーザの Web ブラウザにセッション Cookie が発行されている ことがポイント
- したがって、Webサイトへのユーザーとしてのログイン有無自体は関係がない(後述)
-
ログイン済 ≒ ユーザの Web ブラウザにセッション Cookie が発行されている ことがポイント
- 対象のWebサイトが、設計上意図しない更新操作を受け付けてしまうことで攻撃が成立する
-
受け付けてしまう ≒HTTP リクエストがバックエンドで処理される ことがポイント
- したがって、HTTPリクエストに対するレスポンスがどのような結果となったかは関係がない
- Webブラウザに不正なレスポンスが表示されることで発生する攻撃はXSS(Cross-Site Scripting) のように別の攻撃として分類される
-
受け付けてしまう ≒HTTP リクエストがバックエンドで処理される ことがポイント
日本における著名なCSRF攻撃としては はまちちゃん騒動 が紹介されることが多い
具体的な攻撃方法については、以下のブログに詳しく説明されていた
攻撃者(はまちや氏)自身で攻撃対象のWebサイト(mixi)に開設していたアカウントの日記機能に悪意のあるWebサイト(はまちや氏の個人サイトの攻撃用ページ)へのリンクが投稿され、
ログイン済の利用者がリンクをクリックすると自身の日記に意図しない投稿がおこなわれてしまう…という脆弱性だったとされる
成立条件
対象のWebサイトが 設計上意図しない更新操作を受け付けてしまう
ことで攻撃が成立してしまうことは既に述べたが、具体的にどのような実装が問題となりうるかについて、一般的なパターンを確認していく
フォームデータ送信を受け付ける POST エンドポイント
近年のWebアプリケーションは SPA(Single Page Application)
と任意のWeb API(RESTなど)を組み合わせてデータの取得や更新をおこなうことが多いが、以前は <form>
タグにより送信されたフォームデータをWebサーバーで処理し、テンプレートエンジンを用いてHTMLに値をレンダリングしたものを新たなページとしてブラウザに返却する形式が一般的であった
例えば、Webサーバから以下の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
に向けて送信される
フォームの表示イメージ
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攻撃を発生させる原因のひとつとなっている
この挙動は現代におけるユースケースから考えると望ましい挙動ではないが、Webに関する仕様の多くが後方互換性を強く意識して改訂されてきている経緯から維持されていると思われる
悪用すると、例えば以下のようなHTMLを evil.com
で公開した上で、何らかの方法でユーザーに evil.com
へアクセスさせることができれば、ユーザーが望まない任意のPOSTリクエストを example.com
宛に送信できる
<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 抑止にあたり重要な意味を持つ ことにも留意する。これについては後述する
ログイン機能が無いWebサイトで深刻な被害が発生した一例として、2012年の パソコン遠隔操作事件 では、問い合わせフォームに対してCSRFへの脆弱性を悪用して犯罪予告文を書き込ませる…というものがあった
データの更新操作が発生する GET エンドポイント
前節で説明した <form>
タグを用いたフォームデータ送信以外の方法でも、脆弱性を持ったエンドポイントに対してHTTPリクエストを発生させることができれば攻撃は成立させられる
最も一般的な方法として、 https://example.com/greeting?say=Hi&to=Mom
に示すようなクエリストリングを付与したURLをWebブラウザやメーラー等でクリックすると、 example.com
に対して以下の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ブラウザの挙動に関連する幾つかの仕様について確認しておく
脆弱性に複数の対策を織り込むことは多層防御 の観点からも望ましい
フォームデータ送信
成立条件
の節ではHTMLフォームからのPOST通信について説明したが、 <form>
タグは特定の属性を付与することで以下の形式のHTTPリクエストを発行することができる
- 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フレームワークの仕様や設定について確認することを推奨する
<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>
{"say": "Go away!!!!!", "to": "Dad", "trash": "="}
なお、次節で説明する同一オリジンポリシーの文脈において、HTMLフォームから送信できる範囲のHTTPリクエスト(に類似したもの)を単純リクエスト(Simple request) として定義している
これは、 HTMLフォームから送信したHTTPリクエストはプリフライトリクエストを発生させない
ということを意味する
この挙動は、同一オリジンポリシーの誕生より前にHTMLフォームが存在していたことから、後方互換性を壊さないために規定されているものらしい
同一オリジンポリシー
Webの世界においては、Webブラウザから複数ドメインに対して通信が発生するケースにおいて、いかにコンテンツの安全性を担保するか…といったことが古くから課題となっており、その文脈において 同一オリジンポリシー(Same-Origin Policy / SOP / 同一生成元ポリシー)
という考え方が浸透している
ページの プロトコル、ポート番号、ホスト名が同一
の場合を同一オリジン、値が異なる場合はクロスオリジンと定義し、リクエストの送信やレスポンスの取得に一定の制限を課すものである
この制限を CORS(Cross-Origin Resource Sharing / オリジン間リソース共有)
と呼ぶ
CORSについて、特にCSRFの文脈においては、プリフライトリクエスト(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ブラウザ以外の環境から発生するリクエストについては基本的に制約は課されない
Cookie の SameSite 属性
近年、CSRFの緩和を目的として導入された仕様として HTTP Cookie の SameSite 属性がある
これは、Webサイトより Set-Cookie
ヘッダでCookie付与をおこなう際に、以下の属性を追加で指定することでクロスサイト(歴史的経緯よりクロスオリジンではないことに注意)での通信においてアクセス先にCookieが付与されなくなる
-
SameSite=Lax
の場合- トップレベルナビゲーション(Webブラウザ上のURL遷移)、かつHTTPメソッドが
GET / HEAD / OPTIONS / TRACE
のいずれかである場合のみCookieが送信される
- トップレベルナビゲーション(Webブラウザ上のURL遷移)、かつHTTPメソッドが
-
SameSite=Strict
の場合- いかなる場合もCookieが付与されない
-
SameSite=None
の場合- いかなる場合もCookieが付与される
一般的なWebサイトにおいて、従来の動作を維持しながらCSRF攻撃への対策をするには、 セッション Cookie に SameSite=Lax
を指定する のが望ましい
この挙動は、外部サイトからAタグ等によりURL遷移でサイトを訪れた際にはリクエストにCookieが付与される一方で、CSRF攻撃のリスクとなり得る別サイトからのPOSTリクエストにはセッションCookieが付与されず、ログインが必要なエンドポイントに対するアクセス全般が不成立となる
ただし、前項で説明したとおりCSRF攻撃は ユーザーのログイン有無自体は直接的な原因とはならず、HTTP リクエストが正規の Web サイトから発行されたものか検証していない場合に成立する(先に説明したパソコン遠隔操作事件の事例を参照)ため、後述する根本対応の実施をおこなうことが望ましい
ちなみに、2022年現在の状況として、一部ブラウザでSameSite属性が未指定の場合のデフォルトの挙動がLaxに変更されているらしく、対策をおこなっていない既存サイトの安全性が向上しつつある
その他にも 2分間ルール
といった複雑な挙動があり、詳細は以下に詳しい
SameSite Cookieに関する解説は以下に詳しい
抑止のための設計・実装
CSRFを抑止するために必要となるアプリケーション設計については、OWASP Cheat Sheet Seriesで詳しく紹介されている
本記事では、中でも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サイトから発行されたものであることが検証できる
攻撃者は、攻撃対象のユーザーが /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
が指定されていれば問題ないような気もするが…?
- OWASPの該当ページにて
REST APIにおける対処についても同様の手法でよく、クライアントサイドのJavaScriptにてHTMLに設定されたトークンを取得し、HTTPリクエストヘッダやペイロードに設定してサーバーサイドまで送信すればよい
より詳細な実装については、利用しているWebアプリケーションフレームワークのドキュメントを参照すべきだが、この時そのフレームワークが採用している手法が Synchronizer Token Pattern
と Double Submit Cookie Pattern
のどちらであるかについては意識する必要がある
ちなみに、本手法についてCSRFトークンをセッション単位でなくHTTPリクエスト単位にローテーションする設計にすると、フォームの二重送信(フォームのSubmitボタン連打や、複数タブにて連続してSubmitをおこなった際に処理が多重に呼び出せてしまう問題)を抑止する機能としても活用できる
NTTデータが開発しているTERASOLUNAにおいてはトランザクショントークンチェック という名前付けがされていた
Double Submit Cookie Pattern
ドキュメントでは基本的にSynchronizer Token Patternの利用を推奨しているが、これはCSRF検証用トークンをセッションCookieに紐付けてサーバサイドにて管理する必要がある
この管理をおこなわずにCSRF対策をおこないたい場合にはDouble Submit Cookie Pattern が利用できる
本パターンでは、CSRF 検証トークンを Cookie に設定して Web ブラウザに返送および管理を委譲した上で、リクエスト時にCookie以外の配送経路(HTTPリクエストヘッダやペイロード)でトークンを取得し、サーバサイドでHTTP リクエスト上のトークン値と Cookie 上のトークン値の比較をおこない、リクエストが正規サイトから送信されたものであることを確認する
処理フローとしては以下のようになる
https://medium.com/@kaviru.mihisara/double-submit-cookie-pattern-820fc97e51f2 より引用
Synchronizer Token Patternではセッションで管理していたCSRFトークンをWebブラウザ上のCookieに保存していることから一見危険性が高いように感じられるが、なぜこの実装で問題ないかについて少し考えたい
CSRF検証トークンが持つべき性質として、以前の説明から以下があることがわかっている
- 攻撃者が知り得ない・推測できない値であること
- セッションIDに紐づく値であること
- クロスサイトからのアクセス時にバックエンドに送信されないこと
1については、ユーザーのセッションCookieはCSRFとは別の脆弱性が無い限りには攻撃者は知ることができず、
2についてはセッションCookieと同様のライフサイクルで生成および送受信されるため問題がない
3に関しても、比較する片方の値はCookieにより配送されているが、もう片方の値をそれ以外の経路にて送信していることから検証をおこなうことができ、結果Synchronizer Token Patternと同等の仕様が満たせていることがわかる
注意点としては、Cookie は状況によって上書きすることができるため、攻撃者が何かしらの手法でユーザーに対して任意のCSRF検証トークンのCookieを強制できれば攻撃を成立させられてしまう
一例として、以下の記事で示されているように通信経路上に攻撃者が介在しているケースにおいては、攻撃者から任意のCookie値を強制されることからサイトを保護することはできない
Double Submit Cookieのアーキテクチャは通信内容が改ざんされないことを前提としているため、Synchronizer Token Patternと比較した際に固有の攻撃リスクが存在する
これについてはドキュメントにて対策が書かれており、サーバーサイドでの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.
例えば、DjangoではデフォルトではDouble Submit CookieパターンをベースにしたCSRF保護がおこなわれているが、前述した問題を考慮した実装がおこなわれている
その他の対策方法
以下の節で記載があるので興味があれば見てみるとよいが、基本的に前述のいずれかの対策を取ることが推奨される
- CookieのSameSite属性の利用
- Originヘッダとサーバサイドのオリジンの比較
- カスタムリクエストヘッダのチェック
- リクエスト前にユーザー操作を実施させる
細かなケースへの対処
対策への考え方がわかったところで、より現実的なケースについて考察する
SPA を静的コンテンツとして配信している場合
最近のWebアプリケーションでよくある構成として、フロントエンドのアプリケーションをSPAとして作成した上で静的サイトとして配信しており、ページの初期ロード時のHTMLにCSRF検証トークンが存在しないケースが考えられる
これについては、ページの初期表示時にログイン状態の取得をおこなうエンドポイント等のHTTPレスポンス内でCSRFトークンを受け取って以降のリクエストで利用すればよい
また、Ajax通信中に発行されたCookieは以降のリクエストにおいては自動的に送信されるため、Double Submit Cookie Patternを利用している場合はリクエストの度に document.cookie の内容を確認し、トークンを送信すればよい
Web サイトへのログイン前の場合
前項までに紹介してきたSameSite属性の登場やブラウザのデフォルト挙動の変化により、現在のCSRF攻撃はログイン後(≒ セッションCookie発行後)については成立させるのが難しくなってきている
むしろ、相対的にログインが必須でないページの方がCSRF攻撃のリスクが高まっている状況だが、これについてはプリセッション(認証を完了する前に事前発行するセッションCookie)を発行すべきだとしている
Djangoでは 匿名セッション
としてデフォルトで有効化されているなど、Webフレームワーク側で予め考慮されているケースも多い
例えば 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ブラウザ上で特定の操作をおこなったことをサーバサイドで検証できるような仕組みを導入することで、リスクを緩和できる
副作用のある GET エンドポイントの場合
GETエンドポイントに対してバックエンドでの更新操作をおこなわないのが原則となる
ただし、メールアドレスのアクティベーション や ログアウト といった処理フローにおいて、ユーザーの利便性からGETエンドポイントでの更新操作を選択したくなるケースは存在するため、実装する場合はリスクについて留意する必要がある
メールアドレスのアクティベーションにおいては、一定時間で期限切れになる、第三者が推測不可能な認証コードを生成した後でURLの一部に設定し、リクエスト受付時に値のチェックをおこなうとよい
なお、これはCSRF攻撃とは関係ないが、一部のブラウザやメールクライアントが処理速度改善を目的としてURL のプリフェッチをおこなう場合があるため、実装にあたってはURLアクセス時に直接バックエンドにリクエストが飛ぶようにはせず、JavaScript等を用いてページの初期ロード時に目的のエンドポイントへリクエストをおこなう設計にするとよい
この時エンドポイントをPOSTにするとCSRFトークンの検証をおこなうことができるのでより安全に実装できる
ログアウトについては、一般的に /logout
などのエンドポイントにGETでアクセスした際にそのままログアウトがおこなわれる場合が多いが、POSTを受け付けるエンドポイントを別途作成し、そちらでCSRFトークンをチェックするように変更するとよい
JWT による認証をおこなっている場合
JWTを利用してセッション管理をおこなっているアプリケーションの場合、 Authorization
HTTPヘッダ等のCookie以外の配送経路を用いている場合はCSRF脆弱性を抑止することができる
以下の記事に詳しい
まとめ
まとめです
- ブラウザの挙動は時代によって変化するので、より根本的な対処となる
Synchronizer Token Pattern
またはDouble Submit Cookie Pattern
を導入しよう 🐂 - 自分で実装せずWebアプリケーションが提供する機能を利用しよう 🌴
- 未ログインのユーザーにとっても発生しうる脆弱性であることを理解しよう 👺
参考資料
記事中で触れられなかったものの参考にした記事です 🙏
Discussion
Cross-Site Request Forgery Prevention Cheat Sheetは読むたびに内容が変更されているように思います。内容が安定しておらず、信頼性が低いと考えられます。
Double Submit Cookieについては、以前以下の記事で内容を批判しました。
これを読んでいただけるとわかると思いますが、Double Submit Cookieについては推進派と反対派が論争をしていて編集合戦の様相を呈しています。私は反対派です。
Cookieの改変については以下の記事および動画で解説をしています。
これは文字通り解釈すると間違いでしょう。攻撃者は自分用のトークンCookieを入手して、それを被害者のブラウザにセットしてやれば改ざんすることなく悪用が可能です。トークンにユーザIDなどを含める等しなければ、CSRF攻撃は検知できないと思います。
JWTをCookieで受け渡しするサイトもあり、この場合はCSRF脆弱になる可能性があります。JWTだからCSRFセーフというわけではなく、トークンをリクエストヘッダで送信するからCSRF脆弱性の余地がないだけで、改ざん検知は関係ないと思います。
ご指摘および詳細に解説を頂きましてありがとうございます🙏
数日中に以下の方向で加筆・修正したいと思います
Double Submit Cookieについて
解答:CSRFの防止策に関するチートシートにツッコミを入れる
はじめ頂いたリンクについて拝読致しましたOWASPのDouble Submit Cookieの説明を読んで漠然と不安を感じていたのですが、危険性のある具体的なケースを例示頂けたことでイメージが湧き大変ありがたかったです
一方で、(徳丸さんの他の記事でも紹介されていますように、)いくつかの著名なWebフレームワークでも現在採用されている手法であることと、本記事はアプリケーション開発者に向けた記事であり、利用しているWebフレームワークが採用している実装をそのまま利用することを推奨する趣旨であることから、Double Submit Cookie を使ってはならない…といった立場は取らないつもりで考えています
つきましては、以下について補足したいと思います
注意点としては、Cookie は状況によって上書きすることができるため、攻撃者が何かしらの手法でユーザーに対して~
の何かしらの手法
についてHTTPSを使ってもCookieの改変は防げないことを実験で試してみた
の記事のケースを紹介し、その場合において Double Submit Cookie は Synchronizer Token Pattern と比較した際に固有の攻撃リスクがあるDouble Submit Cookie のトークン発行時の値の暗号化について
こちらについては、ご指摘頂いている
トークンにユーザIDなどを含める等しなければ
のニュアンスが漏れてしまっていることが問題であり、その旨を加筆する必要があると理解しました検証用トークンについて、Cookieで配送する以上は外部から特定値を強制されるリスクがあるため、そのユーザーのコンテキスト(≒セッション)固有に発行されたものであることが復号化時に判断可能であることが必須要件である…という記載が必要であると考えています
JWTについて
私がJWTについて本番環境での利用経験がなく、特に認識が曖昧なまま書いてしまっていたものと思います
JWTをCSRF対策に活用できるのは配送経路にHTTPヘッダ等を利用した(Cookieを用いなかった)場合に限られることと、改ざん検知の下りは記載を削除します
(これについては、そもそも参考として貼り付けていた記事自体にもそのような注意喚起がされていたものでしたので、杜撰な記述をしてしまっていました…今後気をつけたいと思います)