おもしろwebサービス開発日記

Ruby や Rails を中心に、web技術について書いています

Rails8.2ではCSRFトークンを使わずにCSRFを防げるようになりそう

RailsではCSRF攻撃を防ぐために、フォームからのリクエスト送信時に自動でトークン(Authenticity Token)を付与して検証する仕組みを持っています。この仕組みがデフォルトで有効になっているため深く考えなくてもセキュアな実装になる点は便利です。一方で、ユーザが普通にサービスを利用しているにもかかわらずトークンの検証に失敗する偽陽性も度々起こり面倒に感じている人も多いように思います。

Rails8.2以降は、そんなトークンの仕組みを使わずにCSRFを防げるようになりそうです。次のPRが先日マージされました。

Use a modern approach for cross-site request forgery protection by rosa · Pull Request #56350 · rails/rails

詳細はこのPRにすべて書かれているのでそれを読んでください。と書きたいところですがそれだとあまりに雑なので日本語で概要を載せておきます。

CSRF攻撃の検証方法がどのように変わるか

モダンブラウザであれば自動で送信されるSec-Fetch-Site ヘッダーの値を見て、same-originもしくはsame-siteの値であれば問題ないと判断します。

config.action_controller.forgery_protection_verification_strategy = :header_onlyとするか、config.load_defaults 8.2とするとSec-Fetch-Siteヘッダーの値だけを見てCSRFの検証を行います。

古いブラウザを使うユーザが一定数いる等、Sec-Fetch-Siteヘッダーがない環境で運用するサービス向けに:header_or_legacy_tokenという設定も用意されています。何も設定されていない場合はこちらがデフォルトの振る舞いになります。

:header_or_legacy_tokenはまずSec-Fetch-Siteヘッダーの検証を試みてsame-originもしくはsame-siteであれば問題ないと判断します。none(アドレスバーにURLを直接入れるなどのユーザ起因で送られるリクエスト)だったりSec-Fetch-Siteヘッダーがない場合はトークンによる検証にフォールバックします。フォールバック時にログが残せるようになっているため、最初は:header_or_legacy_tokenで運用して影響範囲を見極めたうえで:header_onlyに変更するという流れができてべんり。

また、次のように書くことで特定のOriginからはCSRFの検証をスキップできるようにもなりました。

protect_from_forgery trusted_origins: %w[ https://trusted.example.com ]

信頼している外部サービスからのリクエストを受け付ける時、これまでは該当エンドポイントをskip_forgery_protectionせざるを得なかったのですが今後はその必要はなくなりますね。

感想

めちゃくちゃ便利でいいじゃん、となっています。早くRails8.2使いたいですね。

ただし、Sec-Fetch-Siteを利用するやり方はトークンを利用した方式の完全上位互換ではなく、めちゃくちゃレアケースですがトークンを利用することで防げる攻撃もあります。次回はそれについて書くつもりです。というかレアケースの方の話を書きたくてブログを書き始めたのですがそこまでたどり着けなかった…。