Action Controller 请求伪造保护
控制器操作通过在您的应用程序渲染的 HTML 中包含一个令牌来防止跨站点请求伪造 (CSRF) 攻击。此令牌作为随机字符串存储在会话中,攻击者无法访问。当请求到达您的应用程序时,Rails
会将接收到的令牌与会话中的令牌进行验证。除了 GET 请求之外,所有请求都会被检查,因为这些请求应该是幂等的。请记住,所有面向会话的请求默认情况下都受 CSRF 保护,包括 JavaScript 和 HTML 请求。
由于 HTML 和 JavaScript 请求通常来自浏览器,因此我们需要确保为 Web 浏览器验证请求真实性。我们可以使用面向会话的验证来处理这些类型的请求,方法是在我们的控制器中使用 protect_from_forgery
方法。
GET 请求不受保护,因为它们没有像写入数据库这样的副作用,也不会泄露敏感信息。JavaScript 请求是一个例外:第三方网站可以使用 <script> 标签来引用您网站上的 JavaScript URL。当您的 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
。此令牌的名称和值必须添加到渲染表单的每个布局中,方法是在 HTML head
中包含 csrf_meta_tags
。
在 Ruby on Rails 安全指南 中了解有关 CSRF 攻击和保护应用程序的更多信息。
- 模块 ActionController::RequestForgeryProtection::ClassMethods
- 模块 ActionController::RequestForgeryProtection::ProtectionMethods
- 类 ActionController::RequestForgeryProtection::CookieStore
- 类 ActionController::RequestForgeryProtection::SessionStore
- A
- C
- F
- G
- M
- N
- P
- R
- U
- V
- X
常量
AUTHENTICITY_TOKEN_LENGTH | = | 32 |
CSRF_TOKEN | = | "action_controller.csrf_token" |
NULL_ORIGIN_MESSAGE | = | <<~MSG |
类公共方法
new(...) 链接
来源:显示 | 在 GitHub 上
# 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) 链接
来源:显示 | 在 GitHub 上
# 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) 链接
来源:显示 | 在 GitHub 上
# 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?() 链接
检查请求中的任何身份验证令牌是否有效。
来源:显示 | 在 GitHub 上
# 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) 链接
来源:显示 | 在 GitHub 上
# 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) 链接
来源:显示 | 在 GitHub 上
# 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) 链接
来源:显示 | 在 GitHub 上
# 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() 链接
表单的身份验证参数。覆盖以提供您自己的。
来源:显示 | 在 GitHub 上
# 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: {}) 链接
为当前请求创建身份验证令牌。
来源:显示 | 在 GitHub 上
# 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) 链接
来源:显示 | 在 GitHub 上
# 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!() 链接
GET 请求在渲染后检查跨域 JavaScript。
来源:显示 | 在 GitHub 上
# 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
marked_for_same_origin_verification?() 链接
如果 verify_authenticity_token
before_action 运行,则验证 JavaScript 响应是否只提供给同源 GET 请求。
来源:显示 | 在 GitHub 上
# 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
mask_token(raw_token) 链接
来源:显示 | 在 GitHub 上
# 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?() 链接
检查跨域 JavaScript 响应。
来源:显示 | 在 GitHub 上
# 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
normalize_action_path(action_path) 链接
来源:显示 | 在 GitHub 上
# 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) 链接
来源:显示 | 在 GitHub 上
# 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) 链接
来源:显示 | 在 GitHub 上
# 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?() 链接
检查控制器是否允许伪造保护。
来源:显示 | 在 GitHub 上
# 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) 链接
来源:显示 | 在 GitHub 上
# 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() 链接
请求中发送的可能的身份验证令牌。
来源: 显示 | 在 GitHub 上
# 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) 链接
来源: 显示 | 在 GitHub 上
# 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) 链接
检查客户端的遮罩令牌,看它是否与会话令牌匹配。本质上是 masked_authenticity_token
的反向操作。
来源: 显示 | 在 GitHub 上
# 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
valid_per_form_csrf_token?(token, session = nil) 链接
来源: 显示 | 在 GitHub 上
# 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?() 链接
通过查看 Origin 标头检查请求是否来自同一个来源。
来源: 显示 | 在 GitHub 上
# 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
verified_request?() 链接
如果请求已验证,则返回 true 或 false。检查
-
它是 GET 或 HEAD 请求吗?GET 应该是安全的和幂等的
-
是否
form_authenticity_token
与参数中给定的令牌值匹配? -
是否
X-CSRF-Token
标头与form_authenticity_token
匹配?
来源: 显示 | 在 GitHub 上
# 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
verify_authenticity_token() 链接
用于验证 CSRF 令牌的实际 before_action。不要直接覆盖它。请改用您自己的伪造保护策略。如果您覆盖它,您将禁用相同来源 <script>
验证。
依靠 protect_from_forgery 声明来标记哪些操作需要进行相同来源请求验证。如果在操作上启用了 protect_from_forgery,则此 before_action 会将其 after_action 标记为验证 JavaScript 响应是否适用于 XHR 请求,确保它们遵循浏览器的相同来源策略。
来源: 显示 | 在 GitHub 上
# 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
verify_same_origin_request() 链接
如果运行了 verify_authenticity_token
(表示我们为此请求启用了伪造保护),那么还要验证我们是否没有提供未经授权的跨源响应。
来源: 显示 | 在 GitHub 上
# 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
xor_byte_strings(s1, s2) 链接
来源: 显示 | 在 GitHub 上
# 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