參考文章·
在這一章節中,我們將會講解什麼叫做CSRF,CSRF常見的漏洞場景,以及CSRF的防禦措施
什麼是CSRF
跨站請求僞造,又被稱爲CSRF,是一個web漏洞,該漏洞可能會導致攻擊者誘導用戶執行該用戶非本意要去執行的操作。該漏洞可導致攻擊者在一定程度上規避同源策略,該策略主要是設計用來阻止不同的網站互相引用對方的資源
CSRF攻擊帶來的影響是什麼
在一個成功的CSRF攻擊中,攻擊者導致受害者執行自己本來不想執行的操作。比如說,這種攻擊可能會導致用戶的賬戶綁定的郵箱被篡改、或者賬戶密碼被篡改、或者執行轉賬操作。取決於操作的特點,嚴重的可能導致攻擊者直接獲取到用戶賬戶的完全控制權,如果被控制的用戶是該web應用的管理員,那麼攻擊者可能會直接擁有該應用的數據以及功能的絕對控制權。
CSRF的原理
要想CSRF攻擊成功,下面這三個關鍵一定要滿足。
- 必須要有一個相關的操作,也就是說必須要有一個web應用的動作是值得攻擊者去誘導的。這個動作可能是一個擁有一定的權限之後才能執行的操作(比如說更改其他用戶的權限),或者跟指定用戶數據相關的操作,比如更改用戶的密碼
- 基於cookie的會話處理,執行這個操作需要發送一個或者更多的HTTP請求,而且用用程序僅僅依靠會話cookie來判斷髮出請求的用戶身份。除此之外,再無其他的用於識別用戶身份的機制。
- 沒有不可預測的請求參數,執行該操作的請求中必須不能包含任何攻擊者不能決定或者猜測出來的值。比如,當我們想篡改用戶的密碼時,如果該應用的機制要求我們必須知道原來的密碼,那我們就無法實施攻擊了。
舉個例子,有一個應用,他的其中一項功能是可以通過發送請求來更改用戶的賬戶郵箱,當用戶執行該操作時,會發出下面這樣的HTTP請求包
POST /email/change HTTP/1.1
Host: vulnerable-website.com
Content-Type: application/x-www-form-urlencoded
Content-Length: 30
Cookie: session=yvthwsztyeQkAPzeQ5gHgTvlyxHfsAfE
[email protected]
這個場景正好符合了CSRF攻擊的三個條件:
- 攻擊者對這種操作感興趣,通過這種操作,攻擊者可以通過出發密碼重置來獲取到受害者用戶賬戶的控制權
- 這個web應用使用了一個會話cookie來識別發出該請求的用戶身份。除此之外,沒有其他的措施來對用戶身份進行識別。
- 攻擊者可以很容易地決定執行該操作所需要的參數。
在這種情況下,攻擊者可以很容易地構造出下面的表單:
<html>
<body>
<form action="https://vulnerable-website.com/email/change" method="POST">
<input type="hidden" name="email" value="[email protected]" />
</form>
<script>
document.forms[0].submit();
</script>
</body>
</html>
如果受害者訪問了攻擊者構造的這個web頁面,那麼受害者的郵箱賬戶將會被重置爲攻擊者設置的郵箱,下面是受害者訪問完該頁面後將會發生的事情。
- 攻擊者頁面將會向該網站發送一個HTTP請求
- 如果此時用戶在該網站中處於登錄狀態,那麼受害者的瀏覽器會自動將受害者在該網站中的會話cookie包含進HTTP請求中
- 這個有漏洞的網站會依然按照正規流程去處理剛纔提交的請求,把該請求當作是受害者提交的請求來進行處理,並將其賬戶郵箱更改爲攻擊者指定的郵箱
注意,雖然CSRF大部分都是和基於cookie會話的應用相關,但是其他的一些不使用cookie作爲用戶身份認證機制的web應用,比如將用戶憑證作爲識別用戶身份的方法,比如HTTP basic認證和基於證書的認證
如何構造CSRF攻擊
手動構造CSRF攻擊通常來講是很麻煩的,尤其是我們想要構造的請求中包含大量的參數時,或者是請求中還包含其他比較奇怪的東西時。構造CSRF攻擊最簡單的的方式就是使用CSRF生成器,這個工具是包含在BurpSuite專業版中的。使用方式如下:
- 使用burp抓取請求包,然後在burp中選取出你想要測試或者利用的一段請求
- 右鍵選擇engagemen tools/Generate CSRF POC
- burp將會自動根據請求包生成相應的表單
- 你可以在burp的poc生成器中進行各種微調,來適應不同情景的要求
- 將生成的html代碼複製到網頁中,使用已經登陸到包含漏洞的網站中的瀏覽器來訪問我們生成的頁面,來測試是否能夠成功達到我們的期望
如何佈置我們的CSRF攻擊
CSRF漏洞利用的佈置方式和反射型XSS是很相近的,通常情況下,攻擊者會在他們控制的web頁面中插入惡意的HTML代碼,然後再誘導用戶去訪問該頁面。這個操作可以通過向受害者發送帶有該頁面鏈接的郵件或者通過其他消息軟件向受害者發送鏈接。或者攻擊者將鏈接插入到了一個網站中,比如說評論,那麼只要有人訪問了該評論中的鏈接,CSRF攻擊也就被觸發了。
不僅如此,一些利用GET進行CSRF攻擊的可以直接將攻擊隱藏到一個URL中,在這種情況下,攻擊者將不需要使用任何的外部網站,而只需要該用戶發送一個鏈接即可,比如下面這個POC
<img src="https://vulnerable-website.com/email/[email protected]">
預防CSRF攻擊
防禦CSRF攻擊最好的方法就是在這些包含敏感操作的請求中使用CSRF token,這個token應該滿足以下三個條件
- 不可預測
- 和用戶會話是綁定的
- 在這些敏感操作被執行時都會進行token的驗證
CSRF token
什麼是CSRF token
CSRF token是一個獨一無二的,祕密的,不可預測的一個值,這個值是由服務端生成的,然後這個token會被傳送給客戶端,當客戶端向服務端發起請求時,服務端會驗證客戶端是否包含有效的csrf token,如果沒有的話,則直接丟棄請求包
CSRF token防禦CSRF攻擊的原理其實是很簡單的,因爲這樣一來,攻擊者就無法自行構造出一個包含有效csrf token的HTTP請求,因爲他們無法得知CSRF token的值
CSRF token應該如何生成
csrf token的生成方式應該滿足絕對的不可預測這一特點,這一點和session token是一致的。
你應該使用密碼學強度的僞隨機數生成器(PRNG),使用時間戳作爲種子。
如果你認爲PRNG的機密強度還不夠,那麼你可以將PRNG生成的隨機數再和你自己隨即出來的一些指定用戶的熵串聯起來,然後將整個字符串使用強哈希算法哈希一下,將這個hash出來的字符串作爲最終的csrf token,這樣一來會給攻擊者製造出更多的障礙,這樣可以避免攻擊者通過訪問該網站獲取CSRF樣本,進而分析出CSRF token的生成方式(感覺這個根本不可能,怎麼可能分析的出來,僅時間戳這一點就很難一直,更別說還有PRNG算法了)。
CSRF token是如何傳輸的
CSRF token是高度機密的,在他們的生命週期中,因該被當作很敏感的數據對待。通常使用的方式是使用HTML表單的一個隱藏域將CSRF token發送給服務端,如下:
<input type="hidden" name="csrf-token" value="CIwNZNlR4XbisJF39I8yWnWX9wX4WFoz" />
如何驗證CSRF token
當CSRF token生成之後就應該和cookie一起存放在server端,當一個請求發送過來時,服務端應該驗證該請求中的token是否與自己保存的一致,如果請求包中直接都沒有token,那麼服務端應該直接丟棄該請求。
一定不能將CSRF token存放在cookie字段中,因爲瀏覽器會自動將cookie提交到服務端。
由於CSRF token是由服務端生成的,所以在我們在訪問web應用時,服務端發回的html頁面中的表單就已經設置了一個隱藏域並將其值設置爲了剛生成的csrf token,這樣一來,就算你訪問了攻擊者精心構造的web頁面,由於csrf token不符合,攻擊者依然無法實施CSRF攻擊
SameSite Cookie
SameSite屬性主要用於在發起跨站請求時控制如何提交cookie,通過設置這個屬性,應用程序可以阻止瀏覽器自動提交cookie,因爲瀏覽器不對cookie進行區分,即使不是這個網站的cookie也會提交上去。
SameSite屬性是在服務端響應報文中的SetCookie字段中進行設置的,有兩個候選值,一個是Lax,另一個是Strict,
SetCookie: SessionId=sYMnfCUrAlmqVVZn9dqevxyFpKZt30NN; SameSite=Strict;
SetCookie: SessionId=sYMnfCUrAlmqVVZn9dqevxyFpKZt30NN; SameSite=Lax;
如果SameSite屬性被設置爲了Strict,那麼瀏覽器將不會提交不同源的Cookie,也就是說在B網站中向A網站發起請求時,瀏覽器是不會自動將A網站的Cookie附加到發給B網站的報文中的,這樣做雖然提高了安全性,但是卻使得用戶的使用體驗變差了,如果用戶在其他的網站中被導向了自己之前已經登陸過的網站,那麼該網站就會顯示該用戶是處於未登陸狀態的,需要用戶重新登陸。
如果SameSite屬性被設置成了Lax,那麼瀏覽器會將不同源的Cookie附加到請求報文中,但是需要滿足下面兩個條件:
- 請求使用的必須是GET方法,如果請求方式是POST,則不會將Cookie附加到請求中
- 由用戶點擊發起的請求會被附加上Cookie,如果是由腳本提交的,則不會附加上Cookie(個人感覺這是很關鍵的一點)
所以即便我們將SameSite設置爲了Lax,依然可以提供一定程度的CSRF防禦,因爲大部分的CSRF攻擊都要通過POST請求來完成。但是依然有兩個地方值得我們提高警惕:
- 一些網站的確會通過GET方法來執行一些敏感操作(實際上這樣做是不規範的)
- 很多web應用和框架對HTTP請求方法的容忍度都是比較高的,也就是說他們當初設計的是通過POST請求方法來完成特定的操作,但是實際情況是,使用GET請求方法也能完成該操作
不建議僅僅依靠SameSite屬性來防禦CSRF,更推薦的方式是結合SameSite和CSRF token兩種方式來防禦CSRF攻擊
常見的CSRF漏洞
大部分的CSRF漏洞是由於CSRF token驗證部分的失誤引起的
下面是我們之前討論的HTTP請求報文加上CSRF token之後的樣子
POST /email/change HTTP/1.1
Host: vulnerable-website.com
Content-Type: application/x-www-form-urlencoded
Content-Length: 68
Cookie: session=2yQIDcpia41WrATfjPqvm9tOkDvkMvLm
csrf=WfF1szMUHhiokx9AHFply5L2xAOfjRkE&[email protected]
CSRF token在POST方法中進行驗證,但是在GET方法中卻不進行驗證
GET /email/[email protected] HTTP/1.1
Host: vulnerable-website.com
Cookie: session=2yQIDcpia41WrATfjPqvm9tOkDvkMvLm
如果沒有CSRF token就不驗證,直接放行,正常的處理應該是直接丟棄HTTP請求
POST /email/change HTTP/1.1
Host: vulnerable-website.com
Content-Type: application/x-www-form-urlencoded
Content-Length: 25
Cookie: session=2yQIDcpia41WrATfjPqvm9tOkDvkMvLm
[email protected]
CSRF token沒有和用戶的session綁定
有些web應用直接弄了個CSRF token池,如果用戶提交的請求中的csrf token能和token池中的任意一個token對應上,就能認證通過,這樣一來,攻擊者可以直接使用自己的賬戶登陸上去,然後獲取到一個CSRF token,然後再利用該CSRF token去構造CSRF請求,也能達到CSRF攻擊的目的。
CSRF token沒有綁定到指定用戶的session上
這種情況通常發生在web應用使用了兩個框架時,一個用來追蹤用戶,一個用來防禦CSRF
POST /email/change HTTP/1.1
Host: vulnerable-website.com
Content-Type: application/x-www-form-urlencoded
Content-Length: 68
Cookie: session=pSJYSScWKpmC60LpFOAHKixuFuM4uXWF; csrfKey=rZHCnSzEp8dbI6atzagGoSYyqJqTz5dv
csrf=RhV7yQDO0xcq9gLEah2WVbmuFqyOq7tY&[email protected]
這個網站設計的是把csrfKey寫到了cookie字段中
也就是說,這個CSRF token和cookie並沒有正確的綁定在一起,如果這個應用包含任何能夠讓攻擊者在受害者機器上設置cookie的功能的話,那麼攻擊者就可以事先先使用自己的賬戶登陸到該網站,獲取一個cookie以及與其對應的csrf token,然後使用這個能夠設置cookie的功能將自己的cookie設置到受害者的電腦上,然後再使用自己的csrf去構造CSRF攻擊請求。
基於Referer字段的CSRF防禦
通過檢查請求是否由該網站發出來防禦CSRF攻擊,因爲CSRF攻擊一般都是從攻擊者的網站發出的請求,這種方式的效率並不太高,而且很容易被bypass
Referer字段是HTTP報文中的一個可選字段,Referer是不小心拼錯的,其實應該是referrer,雙寫r,但是RFC就是這麼寫的,也就一直這麼錯過來了,該字段的值表示請求來源於哪一個網站,這個是由瀏覽器自動加上的,有各種各樣的方法可以更改這個字段的值
驗證referer字段取決於發送給服務端的HTTP請求
如果提交給服務端的http請求中並沒有Referer字段,那麼驗證就會直接忽略,攻擊者可以通過構造HTML來告知瀏覽器不要發送referer字段,最簡單的方式就是使用META標籤
<meta name="referrer" content="never">
Referer字段的驗證可以被bypass
有些應用驗證referer字段的方式着實是有些天真,他們只看Referer字段的值中是否包含自家的域名,那麼攻擊者可以很簡單地通過這種下面這種方式繞
http://attacker-website.com/csrf-attack?vulnerable-website.com
這樣發送過去的請求中的Referer字段就是http://attacker-website.com/csrf-attack?vulnerable-website.com
,如果按照上面的驗證方式,則可以簡單通過驗證
還有一種驗證是驗證referer字段的值的起始字符串是否爲自己的域名,其實這種也是有辦法繞過的,攻擊者將有漏洞的網站的域名註冊爲自己的子域名即可
http://vulnerable-website.com.attacker-website.com/csrf-attack
其實有了上面META標籤,我們根本沒必要使用繞過這種方式來進行CSRF攻擊,直接不讓瀏覽器發送Referer字段就行了