LoginSignup
4
4

More than 5 years have passed since last update.

Rails request_forgery_protection に関するメモ

Posted at

production 環境を local に構築、動作確認を試みたとき、csrf で引っかかった。
その時の備忘。
ポイントは action_controllerrequest_forgery_protection.rbverified_request? メソッド。

actionpack-5.0.3/lib/action_controller/metal/request_forgery_protection.rb
      # 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

4つのメソッドを評価し、true/false を判定する。
先頭の3メソッドは、configやhttp method の判定なので、基本的には問題にならない。
ポイントは、後半の2つ。

valid_request_origin?

request.origin と request.base_url が等価であることを評価している。

actionpack-5.0.3/lib/action_controller/metal/request_forgery_protection.rb
      # 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

前段に nginx を配置する場合、request の header に $schemeをリレーする必要がある。

    location / {
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP  $remote_addr;
        proxy_set_header Client-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;  # この行
        proxy_pass http://localhost:3000;
    }

そして、前段に aws ELB が配置される場合は、以下$http_x_forwarded_proto をリレーする。

  location / {
    proxy_set_header Host $host;
    proxy_set_header X-Real-IP  $remote_addr;
    proxy_set_header Client-IP $remote_addr;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header X-Forwarded-Proto $http_x_forwarded_proto;  # この行
    proxy_pass http://localhost:3000;
  }
}

any_authenticity_token_valid?

ここで、具体的にPOSTに付帯する token を評価する。

      # Checks if any of the authenticity tokens from the request are valid.
      def any_authenticity_token_valid?
        request_authenticity_tokens.any? do |token|
          valid_authenticity_token?(session, token)
        end
      end

      # Possible authenticity tokens sent in the request.
      def request_authenticity_tokens
        [form_authenticity_param, request.x_csrf_token]
      end

formのparams と header をそれぞれ評価する。

具体的な評価は、

  • compare_with_real_token(csrf_token, session)
  • valid_per_form_csrf_token?(csrf_token, session)

で実施する。

compare_with_real_token

session[:_csrf_token]にtokenを保管し、これと比較評価している。

      def compare_with_real_token(token, session)
        ActiveSupport::SecurityUtils.secure_compare(token, real_csrf_token(session))
      end

      def real_csrf_token(session)
        session[:_csrf_token] ||= SecureRandom.base64(AUTHENTICITY_TOKEN_LENGTH)
        Base64.strict_decode64(session[:_csrf_token])
      end

production環境のsession_storeで secure を指定していると、httpで動作させた場合にsessionにsotreされなくなるので注意。

config/initializers/session_store.rb
  Rails.application.config.session_store :cookie_store, key: '_truckers_session', secure: Rails.env.production?
4
4
0

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
4
4