首先我們通過一個演示,來介紹一下CSRF攻擊。

作為普通用戶Alice,首先登錄了我們的zoobar網站,在這個網站上看看朋友的profile,轉兩個bar給好朋友Cindy,另一個好朋友Diana又轉給她3個。然後她發現了有個用戶Bob的profile中寫了一個非常引人注意的URL,是一個萌貓網站。於是Alice很開心地點進去了,看了一會兒貓咪之後,她關掉了瀏覽器,完全沒有注意到自己的bar已經少了。

【大家可以把上面的這個過程當作是銀行轉賬,對問題的嚴重性可以有更直觀的感覺。】

CSRF是Cross-Site Request Forgery的簡稱,就是跨站請求偽造。這次課我們就好好理解一下這個攻擊是怎麼回事。

在上面的演示中,Alice是通過攻擊者Bob的鏈接點擊到了攻擊網站,實際上,即使Alice首先關閉了頁面或者瀏覽器,只要她沒有點擊退出網站,或者只要她的cookie還沒有過期,那麼她訪問攻擊者網站的時候,她的zoobar就會被偷走。

為了理清楚,我們得從頭開始看看轉發zoobar的過程。在普通用戶看來,zoobar網站提供了轉賬功能,是為了促進朋友感情的,我自己可以選擇給哪個好朋友轉多少bar,除了我自己外誰也不能轉。

但是,從伺服器的角度來看,這個transfer的過程是怎麼樣的?

結合網站的代碼,我們可以發現,zoobar網站的transfer頁面上,有一個表單,表單的action目標是transfer.php

,然後表單中有幾個項,分別是轉給誰,轉多少。那網站是怎麼判斷是誰要轉bar給其他人的呢?Alice登錄之後,網站就給她設了一個cookie,然後Alice每次請求一個新網頁的時候,瀏覽器都會自動把cookie帶上,幫助網站識別Alice。網站在接收到請求和Cookie之後,就進行一下資料庫操作,把Alice對應的bar的個數減掉兩個,把Cindy對應的個數加上兩個。

這裡我們要再強調一遍,因為cookie非常重要,所以瀏覽器對Cookie的必要保護是有的。它會自動判斷當前網頁請求的URL,會根據cookie所屬的URL判斷發送哪個cookie過去。譬如Alice如果在zoobar網站,然後訪問到惡意網址,絕對不會出現說,惡意網址可以拿到Alice的zoobar網站cookie的情況。

也就是說,不是攻擊者Bob先想辦法從瀏覽器中騙到了Alice的Cookie,在這個實驗中,攻擊者沒辦法做到這一點。但是,攻擊者仍然可以利用到Alice的cookie來完成偷竊。

CSRF攻擊是通過利用瀏覽器來完成的。為了維持HTTP協議和網站的正常運行,瀏覽器不得不每次訪問網站的時候都主動的攜帶上自己保存的相應網站的cookie。所以,攻擊者所要做的,就是主動發出去一個對目標網站的請求就可以了。這就是該攻擊名字的由來——跨站請求「偽造」。本來應該是用戶自己主動的行為發出請求,卻被攻擊者偽造了。

只要用戶當前瀏覽器中還保留著目標網站如Zoobar(銀行)的cookie,然後又瀏覽了惡意攻擊網站,而攻擊網站又主動發出了對目標網站的請求,瀏覽器就會主動地攜帶上目標網站的Cookie。目標網站很難區分這個請求到底是目標網站發出來的,還是攻擊者發出來的。所以,有一條安全規則是:當完成網站使用後,請點擊登出網站,而不是直接點右上角的×,因為登出會銷毀Cookie。

理解了原理之後,我們來看看這個攻擊的代碼實現。CSRF攻擊實現的主要事項是偽造這個請求。有很多種方法可以實現。

首先,攻擊者要偽造這個請求,必須知道zoobar網站發給伺服器的請求長什麼樣。這個怎麼知道呢?

答案很簡單,只要註冊進入網站,這個請求是前端發送過去的,右鍵查看源代碼,在頁面上一清二楚。

<form method=POST name=transferform
action="/transfer.php">

<p>Send <input name=zoobars type=text value="" size=5> zoobars</p>
<p>to <input name=recipient type=text value=""></p>
<input type=submit name=submission value="Send">
</form>

所以,攻擊者只要將以上transfer的頁面上的表單代碼拷貝一下,放到自己的網站上就可以了。

【相對而言,如果使用GET方法提供表單確實要更簡單一點,只需要構造一個URL,誘導受害者去點擊就行了。不過即使使用POST,攻擊者也同樣可以偽造】

此時的攻擊者網頁大概長這樣;用戶當然不會傻到要在攻擊者網站填上要發給attacker自己的錢物。但是沒關係,攻擊者可以自己寫頁面,把值默認填好;攻擊者也不會傻到要去點擊send按鈕。但是攻擊者繞過這一點的方法多得是。

攻擊者可以把按鈕上的文字描述改一下,改成「點擊贏大獎」,「點擊贏華為Mate20」等,各位會不會有興趣去點一下?

估計講到這裡,大家心裡正在默默地下決心,以後打開不常用網站的網頁,不管什麼按鈕我都不點。這個是很好的,但是足夠有用嗎?

很可惜的是,只有這一點,還不夠防禦CSRF。

因為之前我們在介紹JS的時候說過,JS可以控制頁面是所有的內容,包括表單。也即,攻擊者在自己的攻擊頁面上寫上表單之後,完全可以自行提交。也即,只要打開頁面,這個表單就提交了,瀏覽器還很貼心地附上了用戶的cookie。

所以,可能打開看到的頁面是這樣的,

而用戶的資產已經被竊取了。

那大家現在心裡在想什麼呢?是想著,不安全的網站堅決不打開嗎?還是想著,這個什麼破網站,居然這麼容易被攻擊?

大家作為計算機專業的學生,要能夠做到,以後自己開發的網站和產品,不會被人這樣罵。

好的,那麼接下來,我們要來考慮一下,怎麼樣能夠防禦CSRF攻擊。

防禦CSRF的話,那第一反應可能是,都怪瀏覽器不好,瀏覽器多事兒把cookie給提交了。跟瀏覽器講講,不讓它提交了。這個可能嗎?技術上當然可以做得到。

但是,來想一下,首先肯定不能說瀏覽器拒絕帶所有的cookie;否則的話,HTTP協議都得改了,才能讓我們能正常上網。退一步,讓瀏覽器拒絕請求網站和目標網站不一致的cookie。但這樣,首先,這種情況有的時候是需要的,否則,每次從第三方網站跳轉的時候都需要重新輸入用戶名和密碼。譬如,剛登錄上一個並不是太重要的網站,譬如微博,然後每次從其他網站跳轉到微博都需要重新輸入也很麻煩;第二,增加了瀏覽器的工作量。瀏覽器需要跟蹤用戶當前的網站,然後判斷目標的URL。

直接改瀏覽器的方法有點不大現實。那麼,再看看有沒有其他方法?

  1. Referer Check

如果不是讓瀏覽器來自動檢查,程序員寫網站的時候順帶檢查一下行不行?從代碼角度而言,絕對是沒問題的。因為正如在之前看過的,用戶發出的HTTP請求中有Referer這一項,就是表示當前的請求是從哪個網站發出去的。這樣,只需要使用代碼來檢查一下就行。譬如這樣:

<script>
if((document.referrer.indexOf(localhost)<0) && (document.referrer.indexOf(zoobar.com)<0)){
alert(document.referrer);
document.location = "http://www.baidu.com";
top.location = document.location;

}
</script>

或者這樣:

<?php
$ref = ($_SERVER[HTTP_REFERER]);
$refData = parse_url($ref);

if($refData[host]!=www.zoobar.com && $refData[host]!=localhost)
if(!strpos($ref,zoobar) && !strpos($ref,localhost))
die("Hotlinking not permitted!");

?>

我們給大家演示一下就明白了,這種方法只是看起來很美好。

為啥嘞?因為用戶請求從瀏覽器發出去之前,瀏覽器可以對它做各種修改。其中有一種,作為對自己隱私非常介意的用戶,可能會主動設置自己的瀏覽器在發出請求的時候不帶Referrer這個選項。

2. 驗證碼

這種方法是強制用戶在轉賬之前必須進行交互,也即需要一個驗證碼,這對於銀行轉賬而言還是非常重要的。但是考慮到用戶體驗友好,不能給所有的操作都加上驗證碼。因此驗證碼只能作為一種輔助手段,不能作為主要解決方案。【即使有驗證碼,當騙子的目光集中到驗證碼的時候,連驗證碼都能騙走。】

3. Anti CSRF Token

攻擊者能夠攻擊成功,一方面是瀏覽器熱心發送了cookie,一方面是攻擊者很容易偽造請求。那有沒有可能讓攻擊者不能偽造請求?給攻擊者的請求偽造設置門檻?

這個防禦思路,把改進的方向調整到了網站本身。CSRF攻擊能夠成功,是因為請求太容易偽造了;如果請求中包括一些攻擊者不能容易獲取的信息,那麼攻擊自然不能成功。

當然,網頁本身還是所有人可見的,那麼就需要讓其中的信息難以簡單複製。換言之,我們需要在表單中添加一些信息,這個信息最好是用戶獨特而且快速變化的,這樣攻擊者及時能看到一個人的信息,他也不能偽造其他人的;及時他獲得了其他人的信息,但是這個信息快速變化,短時間不用便會過期。另外,添加這樣的信息最好不要影響正常用戶的使用,維持用戶友好。

幸好,這樣的事情其實很容易做到。現在業界對CSRF的防禦,就是這個思路,使用一個Token(Anti CSRF Token)。

  • 用戶登錄網站,服務端生成一個Token,放在用戶的Session
  • 在頁面表單附帶上Token參數,為了不影響用戶,可以設置type=hidden
  • 用戶提交請求時,表單中的這一參數會自動提交, 服務端驗證表單中的Token是否與用戶Session中的Token一致,一致為合法請求,不是則非法請求
  • 每次提交,token值可以更新

因為這個Token值是每個用戶不同,並且開發人員可以設置粒度,用戶每次登錄不同,還是每次提交不同,這樣徹底使得攻擊者無法偽造請求。

最終的頁面可能長這個樣子:

<form method=POST name=transferform action="/transfer.php">

<input name=token type=hidden value="09d6dde682f36904cd58e43cd0e03d59">
<p>Send <input name=zoobars type=text value="" size=5> zoobars</p>
<p>to <input name=recipient type=text value=""></p>
<input type=submit name=submission value="Send">
</form>

以上就是對CSRF攻擊原理和防禦的介紹,希望從這個例子中,大家可以有所體會,攻擊者的腦洞開的一向要比開發者的大。開發者往往為了用戶體驗設計的一些功能,會被黑客利用。同時,攻防是螺旋上升的,黑客提出攻擊方法,開發者也會想法子防禦。現在一些網站開發框架生成form表單時,自動帶有隱藏的token項。作為開發者,必須得了解防禦。


推薦閱讀:
查看原文 >>
相关文章