同步令牌模式防範CSRF跨站請求僞造攻擊

什麼是“跨渣請求僞造”呢?這是信息安全領域的一個名詞,譯自英文“Cross Site Request Forgery”。

百度百科上介紹的很簡單卻很明瞭,大家可以看一下,我這裏配合一些代碼稍微多說一點。

 

假設我們要在銀行網站上給老媽轉100塊錢,畢竟畢業這麼多年了也沒給過家裏錢(雖然你認爲他們都在賺錢不需要你給,況且你自己現在賺錢剛好可以經濟獨立,不過實際上爹媽還是很希望你能支援家裏的)。這個轉賬的HTTP請求類似於這樣:

POST /transfer HTTP/1.1
Host: bank.example.com
Cookie: JSESSIONID=randomid; Domain=bank.example.com; Secure; HttpOnly
Content-Type: application/x-www-form-urlencoded

amount=100.00&routingNumber=1234&account=9876

 你現在已經登錄銀行的網站了(你說你用的客戶端?那當然沒有這個危險性了,所以銀行才都推薦U盾的),然後你新開了個標籤頁,進入一個“危機四伏”的網站(你傻啊,爲什麼要這樣做。其實當別人沒騙的時候我們都可以馬後炮,但是沒有足夠的防範意識,任何時候我們被騙都是在鼓裏)。

這個網站有一個鏈接,代碼類似於這樣:

<form action="https://bank.example.com/transfer" method="post">
  <input type="hidden"      name="amount"      value="100.00"/>
  <input type="hidden"      name="routingNumber"      value="evilsRoutingNumber"/>
  <input type="hidden"      name="account"      value="evilsAccountNumber"/>
  <input type="submit"      value="點我拿紅包!"/>
</form>

 除了一個“拿紅包”的按鈕之外,其他都是隱藏域。一看搶紅包了,趕緊搶吧!這個表單提交後,和上面的請求是完全一樣的。儘管這個網站沒有你的cookie信息,可是你提交的時候cookie照樣會被髮送,所以它完全不需要拿到你的cookie。

你說:好像只要我足夠小心,不去點擊按鈕就行了。當然不是,因爲給你個按鈕讓你點是貌似愚蠢的做法,這個網站打開的同時,它的javascript代碼說不定就不停的發生那個請求了。而你完全不知道。

 

爲什麼會這樣?

因爲接收action的服務器並不知道請求是跨站的,跨不跨站對於服務器來說沒什麼兩樣。

知道了這一點,我們的解決方法也就應運而生了:增加點東西讓其他站點提供不了(因爲只用cookie是不夠的),這樣服務器去驗證這個域不就可以了嗎。

一種方案是同步令牌模式。你的每一次請求,除了session數據外,都會提供服務器經response返回的隨機串作爲一個單獨的http參數。提交後服務器會驗證這個隨機串。而其他網站是拿不到這個隨機串的。

與之前的請求相比,只多了一個參數_csrf:

POST /transfer HTTP/1.1
Host: bank.example.com
Cookie: JSESSIONID=randomid; Domain=bank.example.com; Secure; HttpOnly
Content-Type: application/x-www-form-urlencoded

amount=100.00&routingNumber=1234&account=9876&_csrf=<secure-random>

對於經過瀏覽器進行訪問和發送接收請求的場景,防範CSRF攻擊都有必要。 

 

 Spring Secure是怎麼防範CSRF攻擊呢?

首先,我們使用spring secure應該保證action的HTTP動詞是合適的,不應該使用GET請求處理編輯性活動。使用POST要好得多。有的框架檢測到token(就是上面提到的隨機串)不合法馬上就將用戶登出要求重新登錄。而Spring是產生一個403狀態。

spring默認是開啓 csrf防範的(如果使用命名空間需要開發者顯示開啓),如果想關閉,可以使用disable()方法:

@EnableWebSecurity
@Configuration
public class WebSecurityConfig extends
   WebSecurityConfigurerAdapter {
  @Override
  protected void configure(HttpSecurity http) throws Exception {
    http.csrf().disable();
  }
}

 最後,要保證在除了GET外的請求中都包含_csrf域。不然,連登出都會失敗(咦,登出不是GET嗎?所以最好用POST)。

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章