Spec-Zone.ru › Ruby on Rails 6.0

модуль 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> для ссылки на JavaScript URL на вашем сайте. Когда ваш JavaScript ответ загружается на их сайте, он выполняется. При помощи тщательно разработанного JavaScript на их стороне, конфиденциальные данные в вашем JavaScript ответе могут быть извлечены. Чтобы предотвратить это, только запросы XmlHttpRequest (известные как XHR или Ajax) разрешены для получения JavaScript ответов.

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

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

Как правило, безопасно исключить запросы XHR из защиты от CSRF (как это делает код выше), потому что запросы XHR могут выполняться только с одного источника. Однако, любой домен третьей стороны с кросс-доменным доступом, разрешенный через CORS, также сможет создавать запросы XHR. Убедитесь, что вы проверили свою конфигурацию CORS перед отключением защиты от подделки для XHR.

Защита от 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 301
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 378
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 421
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 248
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 273
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 279
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 320
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 284
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 451
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 401
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 426
def protect_against_forgery? # :doc:
  allow_forgery_protection
end

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

real_csrf_token(session) Показать исходный код
# File actionpack/lib/action_controller/metal/request_forgery_protection.rb, line 396
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 308
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 370
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 339
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 382
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 441
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 295
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 233
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 263
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 409
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–2019 David Heinemeier Hansson
Licensed under the MIT License.

Spec-Zone.ru

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