Spec-Zone.ru › Ruby on Rails 8.1

модуль ActionController::RequestForgeryProtection

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

Защита контроллера Action от подделки запросов

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

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

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

Подклассы ActionController::Base по умолчанию защищены стратегией :exception, которая вызывает ошибку ActionController::InvalidAuthenticityToken для непроверенных запросов.

API могут захотеть отключить это поведение, поскольку обычно они не хранят состояние: то есть клиент, отправляющий запрос API, самостоятельно управляет сессией, а не Rails. Один из способов добиться этого — использовать вместо этого стратегию :null_session, которая позволяет обрабатывать непроверенные запросы, но с пустой сессией:

class ApplicationController < ActionController::Base
  protect_from_forgery with: :null_session
end

Обратите внимание, что приложения API не подключают этот модуль или промежуточное ПО сессий по умолчанию, поэтому для них не требуется настраивать защиту от CSRF.

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

Подробнее об атаках CSRF и защите приложения читайте в руководстве по безопасности Ruby on Rails.

Константы

AUTHENTICITY_TOKEN_LENGTH
CSRF_TOKEN

Открытые методы класса

new (...) Показать исходный код
# File actionpack/lib/action_controller/metal/request_forgery_protection.rb, line 373
def initialize(...)
  super
  @_marked_for_same_origin_verification = nil
end
Вызывает метод суперкласса

Открытые методы экземпляра

commit_csrf_token (request) Показать исходный код
# File actionpack/lib/action_controller/metal/request_forgery_protection.rb, line 383
def commit_csrf_token(request) # :doc:
  csrf_token = request.env[CSRF_TOKEN]
  csrf_token_storage_strategy.store(request, csrf_token) unless csrf_token.nil?
end
reset_csrf_token (request) Показать исходный код
# File actionpack/lib/action_controller/metal/request_forgery_protection.rb, line 378
def reset_csrf_token(request) # :doc:
  request.env.delete(CSRF_TOKEN)
  csrf_token_storage_strategy.reset(request)
end

Закрытые методы экземпляра

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

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

compare_with_global_token (token, session = nil) Показать исходный код
# File actionpack/lib/action_controller/metal/request_forgery_protection.rb, line 557
def compare_with_global_token(token, session = nil) # :doc:
  ActiveSupport::SecurityUtils.fixed_length_secure_compare(token, global_csrf_token(session))
end
compare_with_real_token (token, session = nil) Показать исходный код
# File actionpack/lib/action_controller/metal/request_forgery_protection.rb, line 553
def compare_with_real_token(token, session = nil) # :doc:
  ActiveSupport::SecurityUtils.fixed_length_secure_compare(token, real_csrf_token(session))
end
csrf_token_hmac (session, identifier) Показать исходный код
# File actionpack/lib/action_controller/metal/request_forgery_protection.rb, line 594
def csrf_token_hmac(session, identifier) # :doc:
  OpenSSL::HMAC.digest(
    OpenSSL::Digest::SHA256.new,
    real_csrf_token(session),
    identifier
  )
end
form_authenticity_param () Показать исходный код
# File actionpack/lib/action_controller/metal/request_forgery_protection.rb, line 614
def form_authenticity_param # :doc:
  params[request_forgery_protection_token]
end

Параметр подлинности формы. Переопределите метод, чтобы использовать собственный параметр.

form_authenticity_token (form_options: {}) Показать исходный код
# File actionpack/lib/action_controller/metal/request_forgery_protection.rb, line 488
def form_authenticity_token(form_options: {}) # :doc:
  masked_authenticity_token(form_options: form_options)
end

Создаёт токен подлинности для текущего запроса.

global_csrf_token (session = nil) Показать исходный код
# File actionpack/lib/action_controller/metal/request_forgery_protection.rb, line 590
def global_csrf_token(session = nil) # :doc:
  csrf_token_hmac(session, GLOBAL_CSRF_TOKEN_IDENTIFIER)
end
mark_for_same_origin_verification! () Показать исходный код
# File actionpack/lib/action_controller/metal/request_forgery_protection.rb, line 446
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 452
def marked_for_same_origin_verification? # :doc:
  @_marked_for_same_origin_verification ||= false
end

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

mask_token (raw_token) Показать исходный код
# File actionpack/lib/action_controller/metal/request_forgery_protection.rb, line 546
def mask_token(raw_token) # :doc:
  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
  encode_csrf_token(masked_token)
end
non_xhr_javascript_response? () Показать исходный код
# File actionpack/lib/action_controller/metal/request_forgery_protection.rb, line 457
def non_xhr_javascript_response? # :doc:
  %r(\A(?:text|application)/javascript).match?(media_type) && !request.xhr?
end

Проверяет ответы JavaScript на наличие межсайтовых запросов.

per_form_csrf_token (session, action_path, method) Показать исходный код
# File actionpack/lib/action_controller/metal/request_forgery_protection.rb, line 583
def per_form_csrf_token(session, action_path, method) # :doc:
  csrf_token_hmac(session, [action_path, method.downcase].join("#"))
end
protect_against_forgery? () Показать исходный код
# File actionpack/lib/action_controller/metal/request_forgery_protection.rb, line 619
def protect_against_forgery? # :doc:
  allow_forgery_protection && (!session.respond_to?(:enabled?) || session.enabled?)
end

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

real_csrf_token (_session = nil) Показать исходный код
# File actionpack/lib/action_controller/metal/request_forgery_protection.rb, line 575
def real_csrf_token(_session = nil) # :doc:
  csrf_token = request.env.fetch(CSRF_TOKEN) do
    request.env[CSRF_TOKEN] = csrf_token_storage_strategy.fetch(request) || generate_csrf_token
  end

  decode_csrf_token(csrf_token)
end
request_authenticity_tokens () Показать исходный код
# File actionpack/lib/action_controller/metal/request_forgery_protection.rb, line 483
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 539
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 509
def valid_authenticity_token?(session, encoded_masked_token) # :doc:
  if !encoded_masked_token.is_a?(String) || encoded_masked_token.empty?
    return false
  end

  begin
    masked_token = decode_csrf_token(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

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

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

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

valid_per_form_csrf_token? (token, session = nil) Показать исходный код
# File actionpack/lib/action_controller/metal/request_forgery_protection.rb, line 561
def valid_per_form_csrf_token?(token, session = nil) # :doc:
  if per_form_csrf_tokens
    correct_token = per_form_csrf_token(
      session,
      request.path.chomp("/"),
      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 635
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 470
def verified_request? # :doc:
  request.get? || request.head? || !protect_against_forgery? ||
    (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 398
def verify_authenticity_token # :doc:
  mark_for_same_origin_verification!

  if !verified_request?
    logger.warn unverified_request_warning_message if logger && log_warning_on_csrf_failure

    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 436
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 602
def xor_byte_strings(s1, s2) # :doc:
  s2 = s2.dup
  size = s1.bytesize
  i = 0
  while i < size
    s2.setbyte(i, s1.getbyte(i) ^ s2.getbyte(i))
    i += 1
  end
  s2
end

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

Spec-Zone.ru

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