[Rails] HTTPローカル環境でCSRFヘッダーオンリー保護を利用可能に
背景: セキュアコンテキストの制約とローカル環境の問題
RailsのCSRF保護では、Sec-Fetch-Siteヘッダーを検証するヘッダーオンリーアプローチが提供されています。しかし、HTTPSを使用しないローカルネットワーク環境では、ブラウザがセキュアコンテキストとして認識しないため、Sec-Fetch-Siteヘッダーが送信されません。この結果、非GETリクエストがCSRF保護により拒否され、ローカル環境でのアプリケーション利用に支障をきたしていました。
この問題はFizzyプロジェクトのディスカッションで報告され、PR #2291で解決されました。本PRはその変更をRails本体にアップストリームしたものです。
技術的変更内容
CSRF検証ロジックの拡張
verified_via_header_only?メソッドに新たな条件分岐が追加されました。
変更前:
def verified_via_header_only?
SAFE_FETCH_SITES.include?(sec_fetch_site_value) ||
(sec_fetch_site_value == "cross-site" && origin_trusted?)
end
変更後:
def verified_via_header_only?
SAFE_FETCH_SITES.include?(sec_fetch_site_value) ||
(sec_fetch_site_value == "cross-site" && origin_trusted?) ||
(sec_fetch_site_value.nil? && !request.ssl? && !ActionDispatch::Http::URL.secure_protocol)
end
新たに追加された条件は以下の3つを同時に満たす場合にリクエストを許可します:
-
sec_fetch_site_value.nil?-Sec-Fetch-Siteヘッダーが存在しない -
!request.ssl?- HTTPリクエストである -
!ActionDispatch::Http::URL.secure_protocol- アプリケーションでSSL強制が無効
重要な点として、Originヘッダーの検証は既存のorigin_trusted?チェックとは独立して常に実行されます。
テストケースの追加
ローカルHTTP環境での動作を保証するため、複数のテストケースが追加されました。
test "allows POST with missing Sec-Fetch-Site header on HTTP when force_ssl is disabled" do
with_secure_protocol(false) do
post :index
assert_response :success
end
end
test "blocks POST without Sec-Fetch-Site header when request is HTTPS" do
@request.set_header "HTTPS", "on"
assert_raises(ActionController::InvalidCrossOriginRequest) do
post :index
end
end
test "blocks POST without Sec-Fetch-Site header when request is HTTP but force_ssl is enabled" do
with_secure_protocol(true) do
assert_raises(ActionController::InvalidCrossOriginRequest) do
post :index
end
end
end
これらのテストにより、以下のシナリオが検証されます:
- HTTP + force_ssl無効: リクエスト許可
- HTTPS環境: リクエスト拒否(既存の安全な動作を維持)
- HTTP + force_ssl有効: リクエスト拒否(本番環境相当の設定では厳格に検証)
既存テストの調整
セッション・Cookie関連のミドルウェアテストでは、CSRF検証を明示的に失敗させるためSec-Fetch-Site: cross-siteヘッダーが追加されました。
header "Sec-Fetch-Site", "cross-site"
get "/foo/write_session"
get "/foo/read_session"
assert_equal "1", last_response.body
post "/foo/read_session" # Read session using POST request failing CSRF check
assert_equal "nil", last_response.body
この変更により、テストの意図がより明確になり、ヘッダー不在時の新しい許可ロジックの影響を受けなくなりました。
セキュリティ上の考慮事項
この変更は、以下の理由により既存のセキュリティレベルを維持しています:
- 限定的な適用範囲: HTTPかつforce_ssl無効という、明確にローカル環境を想定した条件下でのみ動作
-
Origin検証の継続:
Sec-Fetch-Siteヘッダーの有無に関わらず、Originヘッダーの検証は常に実行される -
本番環境への影響なし: HTTPS環境や
force_sslが有効な本番環境では従来通りの厳格な検証が継続
影響を受けるユースケース
以下のような環境でヘッダーオンリーCSRF保護が利用可能になります:
- 社内ネットワークでHTTPを使用するアプリケーション
- 開発環境でHTTPSを使用しない場合(
config.force_ssl = false) - HTTPでアクセスされるイントラネットアプリケーション
これらの環境でも、クロスサイトリクエストからの保護はOriginヘッダー検証により維持されます。