Spec-Zone.ru › Ruby on Rails 8.0

модуль ActionController::RequestForgeryProtection

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

Защита от подделки межсайтовых запросов в контроллере

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

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

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

Подклассы 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
NULL_ORIGIN_MESSAGE

Публичные методы класса

new(...) Показать исходный код
# File actionpack/lib/action_controller/metal/request_forgery_protection.rb, line 364
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 374
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 369
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 467
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 548
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 544
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 585
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 605
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 479
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 581
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 437
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 443
def marked_for_same_origin_verification? # :doc:
  @_marked_for_same_origin_verification ||= false
end

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

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

Проверка на ответы JavaScript с другого источника.

normalize_action_path(action_path) Показать исходный код
# File actionpack/lib/action_controller/metal/request_forgery_protection.rb, line 635
def normalize_action_path(action_path) # :doc:
  uri = URI.parse(action_path)

  if uri.relative? && (action_path.blank? || !action_path.start_with?("/"))
    normalize_relative_action_path(uri.path)
  else
    uri.path.chomp("/")
  end
end
normalize_relative_action_path(rel_action_path) Показать исходный код
# File actionpack/lib/action_controller/metal/request_forgery_protection.rb, line 645
def normalize_relative_action_path(rel_action_path) # :doc:
  uri = URI.parse(request.path)
  # add the action path to the request.path
  uri.path += "/#{rel_action_path}"
  # relative path with "./path"
  uri.path.gsub!("/./", "/")

  uri.path.chomp("/")
end
per_form_csrf_token(session, action_path, method) Показать исходный код
# File actionpack/lib/action_controller/metal/request_forgery_protection.rb, line 574
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 610
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 566
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 474
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 530
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 500
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 = 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 552
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 625
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 461
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 389
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 427
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 593
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