Spec-Zone.ru › Ruby on Rails 5.2

модуль ActionController::RequestForgeryProtection

Включенные модули:
AbstractController::Helpers, AbstractController::Callbacks

Действия контроллеров защищены от атак типа Cross-Site Request Forgery (CSRF) путём включения маркера в рендеренном HTML для вашего приложения. Этот маркер хранится как случайная строка в сессии, к которой злоумышленник не имеет доступа. Когда запрос достигает вашего приложения, Rails проверяет полученный маркер с маркером в сессии. Все запросы проверяются, за исключением запросов GET, так как они должны быть идемпотентными. Имейте в виду, что все запросы, ориентированные на сессию, должны быть защищены от CSRF, включая JavaScript и HTML запросы.

Поскольку HTML и JavaScript запросы, как правило, выполняются из браузера, нам необходимо убедиться, что мы проверяем подлинность запроса для веб-браузера. Мы можем использовать аутентификацию, ориентированную на сессию, для этих типов запросов, используя метод protect_from_forgery в наших контроллерах.

Запросы GET не защищены, так как они не имеют побочных эффектов, таких как запись в базу данных, и не раскрывают конфиденциальную информацию. Запросы JavaScript являются исключением: сторонний сайт может использовать тег <script> для ссылки на URL JavaScript на вашем сайте. Когда ваш JavaScript ответ загружается на их сайте, он выполняется. С тщательно разработанным JavaScript на их стороне, конфиденциальные данные в вашем JavaScript ответе могут быть извлечены. Чтобы предотвратить это, только запросы XmlHttpRequest (известные как XHR или Ajax) разрешены для выполнения запросов GET для JavaScript ответов.

Важно помнить, что запросы XML или JSON также затронуты, и если вы создаёте API, вы должны изменить метод защиты от подделки в ApplicationController (по умолчанию: :exception):

class ApplicationController < ActionController::Base
  protect_from_forgery unless: -> { request.format.json? }
end

Защита от CSRF включается с помощью метода protect_from_forgery. По умолчанию protect_from_forgery защищает вашу сессию с помощью метода :null_session, который предоставляет пустую сессию во время запроса.

Мы можем захотеть отключить защиту от CSRF для API, так как они, как правило, предназначены для бессостоятельного функционирования. То есть, клиент API будет обрабатывать сессию за вас вместо Rails.

Параметр маркера по умолчанию называется authenticity_token. Имя и значение этого маркера должны быть добавлены в каждый макет, отображающий формы, путём включения csrf_meta_tags в HTML head.

Узнайте больше об атаках CSRF и защите вашего приложения в Руководстве по безопасности Ruby on Rails.

Константы

AUTHENTICITY_TOKEN_LENGTH
NULL_ORIGIN_MESSAGE

Приватные методы экземпляра

any_authenticity_token_valid?() Показать исходный код
# File actionpack/lib/action_controller/metal/request_forgery_protection.rb, line 295
def any_authenticity_token_valid? # :doc:
  request_authenticity_tokens.any? do |token|
    valid_authenticity_token?(session, token)
  end
end

Проверяет, валиден ли какой-либо из токенов подлинности из запроса.

compare_with_real_token(token, session) Показать исходный код
# File actionpack/lib/action_controller/metal/request_forgery_protection.rb, line 372
def compare_with_real_token(token, session) # :doc:
  ActiveSupport::SecurityUtils.fixed_length_secure_compare(token, real_csrf_token(session))
end
form_authenticity_param() Показать исходный код
# File actionpack/lib/action_controller/metal/request_forgery_protection.rb, line 410
def form_authenticity_param # :doc:
  params[request_forgery_protection_token]
end

Параметр подлинности формы. Переопределите для предоставления собственного.

handle_unverified_request() Показать исходный код
# File actionpack/lib/action_controller/metal/request_forgery_protection.rb, line 242
def handle_unverified_request # :doc:
  forgery_protection_strategy.new(self).handle_unverified_request
end
mark_for_same_origin_verification!() Показать исходный код
# File actionpack/lib/action_controller/metal/request_forgery_protection.rb, line 267
def mark_for_same_origin_verification! # :doc:
  @marked_for_same_origin_verification = request.get?
end

Запросы GET проверяются на межсайтовый JavaScript после рендеринга.

marked_for_same_origin_verification?() Показать исходный код
# File actionpack/lib/action_controller/metal/request_forgery_protection.rb, line 273
def marked_for_same_origin_verification? # :doc:
  @marked_for_same_origin_verification ||= false
end

Если метод verify_authenticity_token before_action был запущен, проверьте, что ответы JavaScript отправляются только запросам GET с одного источника.

masked_authenticity_token(session, form_options: {}) Показать исходный код
# File actionpack/lib/action_controller/metal/request_forgery_protection.rb, line 314
def masked_authenticity_token(session, form_options: {}) # :doc:
  action, method = form_options.values_at(:action, :method)

  raw_token = if per_form_csrf_tokens && action && method
    action_path = normalize_action_path(action)
    per_form_csrf_token(session, action_path, method)
  else
    real_csrf_token(session)
  end

  one_time_pad = SecureRandom.random_bytes(AUTHENTICITY_TOKEN_LENGTH)
  encrypted_csrf_token = xor_byte_strings(one_time_pad, raw_token)
  masked_token = one_time_pad + encrypted_csrf_token
  Base64.strict_encode64(masked_token)
end

Создает замаскированную версию токена подлинности, которая меняется в каждом запросе. Маскировка используется для снижения атак SSL, таких как BREACH.

non_xhr_javascript_response?() Показать исходный код
# File actionpack/lib/action_controller/metal/request_forgery_protection.rb, line 278
def non_xhr_javascript_response? # :doc:
  content_type =~ %r(\Atext/javascript) && !request.xhr?
end

Проверка межсайтовых ответов JavaScript.

normalize_action_path(action_path) Показать исходный код
# File actionpack/lib/action_controller/metal/request_forgery_protection.rb, line 440
def normalize_action_path(action_path) # :doc:
  uri = URI.parse(action_path)
  uri.path.chomp("/")
end
per_form_csrf_token(session, action_path, method) Показать исходный код
# File actionpack/lib/action_controller/metal/request_forgery_protection.rb, line 395
def per_form_csrf_token(session, action_path, method) # :doc:
  OpenSSL::HMAC.digest(
    OpenSSL::Digest::SHA256.new,
    real_csrf_token(session),
    [action_path, method.downcase].join("#")
  )
end
protect_against_forgery?() Показать исходный код
# File actionpack/lib/action_controller/metal/request_forgery_protection.rb, line 415
def protect_against_forgery? # :doc:
  allow_forgery_protection
end

Проверяет, разрешает ли контроллер защиту от подделки.

real_csrf_token(session) Показать исходный код
# File actionpack/lib/action_controller/metal/request_forgery_protection.rb, line 390
def real_csrf_token(session) # :doc:
  session[:_csrf_token] ||= SecureRandom.base64(AUTHENTICITY_TOKEN_LENGTH)
  Base64.strict_decode64(session[:_csrf_token])
end
request_authenticity_tokens() Показать исходный код
# File actionpack/lib/action_controller/metal/request_forgery_protection.rb, line 302
def request_authenticity_tokens # :doc:
  [form_authenticity_param, request.x_csrf_token]
end

Возможные токены подлинности, отправленные в запросе.

unmask_token(masked_token) Показать исходный код
# File actionpack/lib/action_controller/metal/request_forgery_protection.rb, line 364
def unmask_token(masked_token) # :doc:
  # Split the token into the one-time pad and the encrypted
  # value and decrypt it.
  one_time_pad = masked_token[0...AUTHENTICITY_TOKEN_LENGTH]
  encrypted_csrf_token = masked_token[AUTHENTICITY_TOKEN_LENGTH..-1]
  xor_byte_strings(one_time_pad, encrypted_csrf_token)
end
valid_authenticity_token?(session, encoded_masked_token) Показать исходный код
# File actionpack/lib/action_controller/metal/request_forgery_protection.rb, line 333
def valid_authenticity_token?(session, encoded_masked_token) # :doc:
  if encoded_masked_token.nil? || encoded_masked_token.empty? || !encoded_masked_token.is_a?(String)
    return false
  end

  begin
    masked_token = Base64.strict_decode64(encoded_masked_token)
  rescue ArgumentError # encoded_masked_token is invalid Base64
    return false
  end

  # See if it's actually a masked token or not. In order to
  # deploy this code, we should be able to handle any unmasked
  # tokens that we've issued without error.

  if masked_token.length == AUTHENTICITY_TOKEN_LENGTH
    # This is actually an unmasked token. This is expected if
    # you have just upgraded to masked tokens, but should stop
    # happening shortly after installing this gem.
    compare_with_real_token masked_token, session

  elsif masked_token.length == AUTHENTICITY_TOKEN_LENGTH * 2
    csrf_token = unmask_token(masked_token)

    compare_with_real_token(csrf_token, session) ||
      valid_per_form_csrf_token?(csrf_token, session)
  else
    false # Token is malformed.
  end
end

Проверяет замаскированный токен клиента, чтобы определить, соответствует ли он токену сеанса. По сути, это обратная функция к masked_authenticity_token.

valid_per_form_csrf_token?(token, session) Показать исходный код
# File actionpack/lib/action_controller/metal/request_forgery_protection.rb, line 376
def valid_per_form_csrf_token?(token, session) # :doc:
  if per_form_csrf_tokens
    correct_token = per_form_csrf_token(
      session,
      normalize_action_path(request.fullpath),
      request.request_method
    )

    ActiveSupport::SecurityUtils.fixed_length_secure_compare(token, correct_token)
  else
    false
  end
end
valid_request_origin?() Показать исходный код
# File actionpack/lib/action_controller/metal/request_forgery_protection.rb, line 430
def valid_request_origin? # :doc:
  if forgery_protection_origin_check
    # We accept blank origin headers because some user agents don't send it.
    raise InvalidAuthenticityToken, NULL_ORIGIN_MESSAGE if request.origin == "null"
    request.origin.nil? || request.origin == request.base_url
  else
    true
  end
end

Проверяет, исходит ли запрос из того же источника, используя заголовок Origin.

verified_request?() Показать исходный код
# File actionpack/lib/action_controller/metal/request_forgery_protection.rb, line 289
def verified_request? # :doc:
  !protect_against_forgery? || request.get? || request.head? ||
    (valid_request_origin? && any_authenticity_token_valid?)
end

Возвращает true или false, если запрос проверен. Проверяет:

  • Это запрос GET или HEAD? GET-запросы должны быть безопасными и идемпотентными

  • Соответствует ли токен form_authenticity_token заданному значению токена из параметров?

  • Соответствует ли заголовок X-CSRF-Token токену form_authenticity_token?

verify_authenticity_token() Показать исходный код
# File actionpack/lib/action_controller/metal/request_forgery_protection.rb, line 227
def verify_authenticity_token # :doc:
  mark_for_same_origin_verification!

  if !verified_request?
    if logger && log_warning_on_csrf_failure
      if valid_request_origin?
        logger.warn "Can't verify CSRF token authenticity."
      else
        logger.warn "HTTP Origin header (#{request.origin}) didn't match request.base_url (#{request.base_url})"
      end
    end
    handle_unverified_request
  end
end

Фактический метод before_action, используемый для проверки токена CSRF. Не переопределяйте его напрямую. Вместо этого предоставьте собственную стратегию защиты от подделки. Если вы переопределите его, вы отключите проверку межсайтовых <script> запросов.

Используйте объявление protect_from_forgery, чтобы отметить действия, требующие проверки запросов с одного источника. Если защита от подделки (protect_from_forgery) включена для действия, этот метод before_action помечает его after_action для проверки того, что ответы JavaScript предназначены только для запросов XHR, гарантируя, что они следуют политике одного источника браузера.

verify_same_origin_request() Показать исходный код
# File actionpack/lib/action_controller/metal/request_forgery_protection.rb, line 257
def verify_same_origin_request # :doc:
  if marked_for_same_origin_verification? && non_xhr_javascript_response?
    if logger && log_warning_on_csrf_failure
      logger.warn CROSS_ORIGIN_JAVASCRIPT_WARNING
    end
    raise ActionController::InvalidCrossOriginRequest, CROSS_ORIGIN_JAVASCRIPT_WARNING
  end
end

Если verify_authenticity_token был выполнен (указывает, что защита от подделки включена для этого запроса), также проверьте, что мы не отправляем несанкционированный ответ с другого источника.

xor_byte_strings(s1, s2) Показать исходный код
# File actionpack/lib/action_controller/metal/request_forgery_protection.rb, line 403
def xor_byte_strings(s1, s2) # :doc:
  s2_bytes = s2.bytes
  s1.each_byte.with_index { |c1, i| s2_bytes[i] ^= c1 }
  s2_bytes.pack("C*")
end

© 2004–2018 David Heinemeier Hansson
Licensed under the MIT License.

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API