1. 程式人生 > >防止CSRF攻擊與protect_from_forgery

防止CSRF攻擊與protect_from_forgery

CSRF(Cross-Site Request Forgery)是一種常見的攻擊手段,Rails中下面的程式碼幫助我們的應用來阻止CSRF攻擊。

class ApplicationController < ActionController::Base
  # Prevent CSRF attacks by raising an exception.
  # For APIs, you may want to use :null_session instead.
  protect_from_forgery with: :exception
end

這段程式碼是Rails4自動生成的,這裡使用了with: :exception

設定了對在handle_unverified_request使用的策略是丟擲異常ActionController::InvalidAuthenticityToken。 Rails3中預設使用的reset_session
Rails防止CSRF的機制是在表單中隨機生成一個authenticity_token,同時儲存於表單的隱藏域以及當前的session中,當表單提交時,而server端就可以比較這兩處的是否一致來做出判斷,判斷請求的來源是否可靠,因為第三方是無法知道session中的token的。

# Sets the token value for the current session.
def form_authenticity_token session[:_csrf_token] ||= SecureRandom.base64(32) end
<div style="margin:0;padding:0;display:inline">
  <input name="utf8" type="hidden" value="✓">
  <input name="authenticity_token" type="hidden" value="EZWDs44j5vzY+DCsgTHL0iPYiOUwaFnemwtGmo2AVRM=">
</div>

當然,這些都是正常情況,當表單要作為ajax提交,也就是data-remote=true時,情況就不同了,預設配置下,authenticityt_token不再自動生成。如果是Rails3就會發現session中的資訊不見了,如果是把user_id儲存在session中的,當然登入的狀態就改變了。如果是Rails4,預設就會得到上面提到的InvalidAuthenticityToken異常。

#form_tag_helper.rb
def html_options_for_form(url_for_options, options)
   options.stringify_keys.tap do |html_options|
     ...
     if html_options["data-remote"] &&
        !embed_authenticity_token_in_remote_forms &&
        html_options["authenticity_token"].blank?
       # The authenticity token is taken from the meta tag in this case
       html_options["authenticity_token"] = false
     elsif html_options["authenticity_token"] == true
       # Include the default authenticity_token, which is only generated when its set to nil,
       # but we needed the true value to override the default of no authenticity_token on data-remote.
       html_options["authenticity_token"] = nil
     end
   end
 end

上面程式碼的5-14行可以看到生成token時的配置判斷,從中也可以得到解決的兩種辦法:
1. 配置

config.action_view.embed_authenticity_token_in_remote_forms = true

2. 通過JS獲取
其實在預設的layout中,一般會有一行<%= csrf_meta_tags %>,它的定義是:

def csrf_meta_tags
  if protect_against_forgery?
    [
      tag('meta', :name => 'csrf-param', :content => request_forgery_protection_token),
      tag('meta', :name => 'csrf-token', :content => form_authenticity_token)
    ].join("\n").html_safe
  end
end

它在頁面的head中增加一個csrf-token的屬性

meta content="authenticity_token" name="csrf-param" />
meta content="VY13wlC2rgGccbkxyvm7Z1WX4LKH+71vzIj+8Um0QO8=" name="csrf-token" />

這與表單渲染出的authenticity_token完全一致,所以這就給了我們通過js來給表單設定authenticity_token的辦法,如下

//application.js
$('input[name=authenticity_token]').val($('meta[name=csrf-token]').attr('content'))