модуль ActionController::RequestForgeryProtection
Действия контроллеров защищены от атак типа 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
Приватные методы экземпляра
# 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 Проверяет, валиден ли какой-либо из токенов подлинности из запроса.
# 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
# File actionpack/lib/action_controller/metal/request_forgery_protection.rb, line 410 def form_authenticity_param # :doc: params[request_forgery_protection_token] end
Параметр подлинности формы. Переопределите для предоставления собственного.
# 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
# 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 после рендеринга.
# 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 с одного источника.
# 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.
# 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.
# 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 # 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 # File actionpack/lib/action_controller/metal/request_forgery_protection.rb, line 415 def protect_against_forgery? # :doc: allow_forgery_protection end
Проверяет, разрешает ли контроллер защиту от подделки.
# 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
# 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
Возможные токены подлинности, отправленные в запросе.
# 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
# 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.
# 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 # 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.
# 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?
# 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, гарантируя, что они следуют политике одного источника браузера.
# 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 был выполнен (указывает, что защита от подделки включена для этого запроса), также проверьте, что мы не отправляем несанкционированный ответ с другого источника.
# 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.