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