165
111

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

RailsのPOSTでCSRF TokenがVerifyできないときに確認したいこと

Posted at

前提条件

  • nginxがSSL Terminationをするリバースプロキシである
  • Railsのバージョンが5の状態でrails newをしている

発生する問題

  • formのPOST時にCan't verify CSRF token authenticity.というエラーが発生する

確認するところ

  • CSRF Tokenがページ内に埋まっているか?
  • nginxにおけるリパースプロキシの設定は間違っていないか?

CSRF Tokenがページ内に埋まっているか?

Railsで入力フォームを作成するときは、おそらくform_forform_tagを使うことになると思います。その場合、普通はauthenticity_tokenというhidden fieldが追加されています。

たとえばこれはQiitaの意見を送信するフォームにauthenticity_tokenが埋め込まれている様子です。
qiita authenticity_token

またAjaxで送信するフォームなどは、代わりにcsrf_meta_tag<head>の中で呼んでやることで、X_CSRF_Tokenをリクエストヘッダーに付与することによってCSRF対策を行っています。

このように、Rails側にCSRF対策のためのtokenを送ることができているかを確認するといいでしょう。

たとえばこの記事を下書き保存したときのRequestをみてみると、このようにauthenticity_tokenを送っているのが確認できます。これはChromeのdeveloper toolsにあるNetworkの欄で確認することができます。
qiita save as draft request

nginxにおけるリバースプロキシの設定は間違っていないか?

Railsをproduction環境で動作させる場合、大抵はApacheやnginxをリバースプロキシとして前段に噛ませるでしょう。その場合に、リパースプロキシがRailsに送るheaderが間違っている(不足している)と、CSRF Tokenの検証に失敗します。

RailsがCSRF Tokenを検証する部分のコードを見てみます。

actionpack/lib/action_controller/metal/request_forgery_protection.rb#L211
      def verify_authenticity_token
        mark_for_same_origin_verification!

        if !verified_request?
          if logger && log_warning_on_csrf_failure
            logger.warn "Can't verify CSRF token authenticity."
          end
          handle_unverified_request
        end
      end

rails/request_forgery_protection.rb at v5.0.0.1 · rails/rails

まずmask_for_same_origin_verification!ですが、中身はこれです。要はGET Requestであればpassします。

actionpack/lib/action_controller/metal/request_forgery_protection.rb#L244
      # GET requests are checked for cross-origin JavaScript after rendering.
      def mark_for_same_origin_verification!
        @marked_for_same_origin_verification = request.get?
      end

rails/request_forgery_protection.rb at v5.0.0.1 · rails/rails

次のverified_request?はこうなっています。

action_controller/metal/request_forgery_protection.rb#L266
      # Returns true or false if a request is verified. Checks:
      #
      # * Is it a GET or HEAD request?  Gets should be safe and idempotent
      # * Does the form_authenticity_token match the given token value from the params?
      # * Does the X-CSRF-Token header match the form_authenticity_token
      def verified_request?
        !protect_against_forgery? || request.get? || request.head? ||
          (valid_request_origin? && any_authenticity_token_valid?)
      end

rails/request_forgery_protection.rb at v5.0.0.1 · rails/rails

protect_against_forgery?config.allow_forgery_protectionの設定値、つまりCSRFの検証をおこなうかどうかを返すメソッドで、デフォルトでtrueです。

その次の2つはhttp requestの種類をみています。GETもしくはHEADなら許可ということですね。

そして次のvalid_request_origin?が先程述べたリパースプロキシの設定と関係してきます。

actionpack/lib/action_controller/metal/request_forgery_protection.rb#L398
      # Checks if the request originated from the same origin by looking at the
      # Origin header.
      def valid_request_origin?
        if forgery_protection_origin_check
          # We accept blank origin headers because some user agents don't send it.
          request.origin.nil? || request.origin == request.base_url
        else
          true
        end
      end

rails/request_forgery_protection.rb at v5.0.0.1 · rails/rails

そしてこのforgery_protection_origin_checkですが、この設定値を見に行きます。

config/initializers/request_forgery_protection.rb
# Be sure to restart your server when you modify this file.

# Enable origin-checking CSRF mitigation.
Rails.application.config.action_controller.forgery_protection_origin_check = true

これはRails 5からデフォルトでtrueになった設定です。

ifの中でrequestのoriginとbase_urlが同一かどうかを検証していますが、リパースプロキシの設定に不足があるとこの検証が通らず、Can't verify CSRF token authenticity.となってしまいます。

僕の環境では、nginxの設定にproxy_set_header X-Forwarded-Proto $scheme;が不足していたためにこのエラーが発生していました。

location \ {
  proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
  proxy_set_header Host $http_host;
  proxy_set_header X-CSRF-Token $http_x_csrf_token;
  proxy_set_header X-Real-IP $remote_addr;
  proxy_set_header X-Forwarded-Proto $scheme; #これがなかった
  proxy_redirect off;

}

まとめ

CSRF対策はしっかり行いましょう。

参考

165
111
2

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
165
111

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?