Action Controller 請求偽造保護
控制器動作透過在應用程式的已呈現 HTML 中包含一個 token,以防止跨網站請求偽造 (CSRF) 攻擊。此 token 儲存在會話中,是一個隨機字串,攻擊者無法存取。當請求到達應用程式時,Rails 會驗證接收到的 token 與會話中的 token。除了 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
- H
- 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 345 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 355 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 350 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 448 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 534 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 530 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 571 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 591 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 460 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 567 def global_csrf_token(session = nil) # :doc: csrf_token_hmac(session, GLOBAL_CSRF_TOKEN_IDENTIFIER) end
handle_unverified_request() 連結
來源:顯示 | 在 GitHub 上
# File actionpack/lib/action_controller/metal/request_forgery_protection.rb, line 381 def handle_unverified_request # :doc: protection_strategy = forgery_protection_strategy.new(self) if protection_strategy.respond_to?(:warning_message) protection_strategy.warning_message = unverified_request_warning_message end protection_strategy.handle_unverified_request end
mark_for_same_origin_verification!() 連結
在渲染後,會檢查 GET 要求是否有跨來源 JavaScript。
來源:顯示 | 在 GitHub 上
# File actionpack/lib/action_controller/metal/request_forgery_protection.rb, line 420 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 426 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 523 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 431 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 621 def normalize_action_path(action_path) # :doc: uri = URI.parse(action_path) uri.path.chomp("/") end
per_form_csrf_token(session, action_path, method) 連結
來源:顯示 | 在 GitHub 上
# File actionpack/lib/action_controller/metal/request_forgery_protection.rb, line 560 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 596 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 552 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 455 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 515 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 483 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 538 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 611 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 442 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 371 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 410 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 579 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