この記事はウィルゲート Advent Calendar 2024 8日目の記事です。
こんにちは、コンテンツ開発ユニットの清水(@takaaki_w)です。
突然ですがあなたは画面上に「CSRF token mismatch.」が出てきた時にすぐに解決できますでしょうか?
↓こんなやつ
私は沼にハマり期末の大事な時期に開発が進みませんでした。
セキュリティ向上のために人知れず頑張ってくれているCSRF tokenさんに対して大人気ない態度を取ってしまった私ですが、懺悔の気持ちとして「CSRF token mismatch.」の沼から抜ける方法を記します。
CSRFとは?
IPA(独立行政法人情報処理推進機構)によると、下記のように紹介があります。
ウェブサイトの中には、サービスの提供に際しログイン機能を設けているものがあります。ここで、ログインした利用者からのリクエストについて、その利用者が意図したリクエストであるかどうかを識別する仕組みを持たないウェブサイトは、外部サイトを経由した悪意のあるリクエストを受け入れてしまう場合があります。このようなウェブサイトにログインした利用者は、悪意のある人が用意した罠により、利用者が予期しない処理を実行させられてしまう可能性があります。このような問題を「CSRF(Cross-Site Request Forgeries/クロスサイト・リクエスト・フォージェリ)の脆弱性」と呼び、これを悪用した攻撃を、「CSRF攻撃」と呼びます。
CSRF攻撃により、発生しうる脅威(脚注1)は次のとおりです。
・ログイン後の利用者のみが利用可能なサービスの悪用
不正な送金、利用者が意図しない商品購入、利用者が意図しない退会処理 等
・ログイン後の利用者のみが編集可能な情報の改ざん、新規登録
各種設定の不正な変更(管理者画面、パスワード等)、掲示板への不適切な書き込み 等
引用:
私はこれまで読み方を知りませんでしたが、「シーサーフ」と読むそうです。
CSRF tokenとは?
Laravelの公式によると、下記のように紹介があります。
Laravelは、アプリケーションによって管理されているアクティブなユーザーセッションごとにCSRF「トークン」を自動的に生成します。このトークンは、認証済みユーザーが実際にアプリケーションへリクエストを行っているユーザーであることを確認するために使用されます。このトークンはユーザーのセッションに保存され、セッションが再生成されるたびに変更されるため、悪意のあるアプリケーションはこのトークンへアクセスできません。
引用:
よくLaravelのbladeファイルのフォーム配下に「@csrf」とありますが、この記述のおかげでリクエストにCSRF tokenを含めることができます。
<?php {!! Form::open( [ 'url' => action('xxxController@xxx', [$Id]), 'method' => 'POST', 'enctype' => 'multipart/form-data' ] ) !!} @csrf <!-- ココ! --> {!! Form::close() !!}
Illuminate\Foundation\Http\Middleware\ValidateCsrfToken
ミドルウェアが、リクエスト入力のトークンがセッションに保存しているトークンと一致するかを自動的に検証しており、ここで例外がスローされると「CSRF token mismatch.」が表示されます。
<?php /** * Handle an incoming request. * * @param \Illuminate\Http\Request $request * @param \Closure $next * @return mixed * * @throws \Illuminate\Session\TokenMismatchException */ public function handle($request, Closure $next) { if ( $this->isReading($request) || $this->runningUnitTests() || $this->inExceptArray($request) || $this->tokensMatch($request) ) { return tap($next($request), function ($response) use ($request) { if ($this->shouldAddXsrfTokenCookie()) { $this->addCookieToResponse($request, $response); } }); } throw new TokenMismatchException('CSRF token mismatch.'); // ココ! }
引用:
私は開発環境上にて、ログイン・ログアウトや、フォーム送信などのPOST処理を行うと「CSRF token mismatch.」エラーが出てしまうため、困っておりました。
※補足
Illuminate\Foundation\Http\Middleware\VerifyCsrfToken
を継承したクラスに特定のパスを記述することでCSRF保護を無効にすることもできるそうです。
<?php namespace App\Http\Middleware; use Illuminate\Foundation\Http\Middleware\VerifyCsrfToken as Middleware; class VerifyCsrfToken extends Middleware { /** * The URIs that should be excluded from CSRF verification. * * @var array */ protected $except = [ 'logout', // 例外としてログアウトルールを設定 ]; }
とはいえセキュリティ上避けるべきだと思います。
試したこと/確認したこと
様々なサイトやGPT先生を参考に下記を試したのですが、私の場合は効果がありませんでした。
- フォームに「@csrf」が記述されているかの確認
storage/framework/sessions
ディレクトリが書き込み可能かの確認- JavaScriptファイルのリクエストヘッダーにCSRFトークンが含まれていること
- サーバー及びブラウザのキャッシュ削除
config/session.php
の設定で、secure オプションがtrueになっていること
<?php 'secure' => env('SESSION_SECURE_COOKIE', true)
- Laravelのdebug_barでresponse_headersにset-cookieが入っていることの確認
「CSRF token mismatch.」の原因
.envの下記の設定箇所がコメントアウトされているのが原因でした
#SESSION_SECURE_COOKIE=false
SESSION_SECURE_COOKIE=false は、セッションのクッキーが HTTP と HTTPS の両方で送信可能であることを示します。 これがコメントアウトされている場合、デフォルト値(=true)が適用され、クッキーが HTTPS のみで送信可能になります。 開発環境では証明書の適用がなく HTTP を使用しているため、HTTP環境でのリクエストでセッションクッキーがブラウザに送信されません。 結果として、CSRFトークンがセッションから正しく取得できず、「CSRF token mismatch.」エラーが発生していたことが分かりました。
コメントアウトを外し、キャッシュをクリアしたら「CSRF token mismatch.」のエラーがでなくなりました。
SESSION_SECURE_COOKIE=false
意図せずコメントアウトされていた理由ですが、誤ってリモートサーバー上でgit pullしていたことが原因だと推測しています。 弊社ではAWS上に開発環境を1人1台立ち上げており、そこにSFTPでファイル送信することで開発を行なっております。本来はローカルでブランチの切り替えなどを行なっておりますが、上述のミスを犯すと悲しいことになるので注意が必要です。
これから/おわりに
この一件を通してLaravel上でどのようにCSRF tokenが設定されているのかを知ることができたので良かったです。 意外と「CSRF token mismatch.」に関する記事が少なかったので、お困りの方のお役に少しでも立てたら幸いです。
「ウィルゲート Advent Calendar 2024」、翌日は武田さんの「初めてTDDやってみたら、身に沁みてTDDの 〝良さ〟 を理解した話」です!
お楽しみに!