什么是CSRF攻擊?
了解CSRF攻擊的最佳方法是看一個具體示例。
例子
假設您銀行的網站提供了一種表格,該表格允許將資金從當前登錄的用戶轉移到另一個銀行帳戶。
例如,轉賬表單可能如下所示:
<form method="post"
action="/transfer">
<input type="text"
name="amount"/>
<input type="text"
name="routingNumber"/>
<input type="text"
name="account"/>
<input type="submit"
value="Transfer"/>
</form>
http 的響請求可能如下:
POST /transfer HTTP/1.1
Host: bank.example.com
Cookie: JSESSIONID=randomid
Content-Type: Application/x-www-form-urlencoded
amount=100.00&routingNumber=1234&account=9876
現在,假裝您對銀行的網站進行身份驗證,然后無需注銷即可訪問一個邪惡的網站。
惡意網站包含具有以下格式的html頁面:
<form method="post"
action="https://bank.example.com/transfer">
<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="Win Money!"/>
</form>
您想贏錢,因此單擊“提交”按鈕。
在此過程中,您無意中將 $100 轉讓給了惡意用戶。
發生這種情況的原因是,盡管惡意網站無法看到您的cookie,但與您的銀行關聯的cookie仍與請求一起發送。
最糟糕的是,使用JAVAScript可以使整個過程自動化。 這意味著您甚至不需要單擊該按鈕。
此外,在訪問受XSS攻擊的誠實站點時,也很容易發生這種情況。
那么,我們如何保護用戶免受此類攻擊呢?
web 安全系列-02-XSS 跨站腳本攻擊
防范CSRF攻擊
發生CSRF攻擊的原因是受害者網站的HTTP請求與攻擊者網站的請求完全相同。
這意味著無法拒絕來自邪惡網站的請求,也不允許來自銀行網站的請求。
為了防御CSRF攻擊,我們需要確保惡意站點無法提供請求中的某些內容,以便我們區分這兩個請求。
Spring提供了兩種機制來防御CSRF攻擊:
- 同步器令牌模式
- 在會話Cookie上指定SameSite屬性
安全方法必須是冪等的
為了使針對CSRF的任何一種保護都起作用,應用程序必須確保“安全” HTTP方法是冪等的。
這意味著使用HTTP方法GET,HEAD,OPTIONS和TRACE進行的請求不應更改應用程序的狀態。
idempotent 冪等性防止重復提交
同步器令牌模式
抵御CSRF攻擊的最主要,最全面的方法是使用同步器令牌模式。
解決方案
該解決方案是為了確保每個HTTP請求除了我們的會話cookie之外,還必須在HTTP請求中提供一個安全的,隨機生成的值,稱為CSRF令牌。
提交HTTP請求時,服務器必須查找預期的CSRF令牌,并將其與HTTP請求中的實際CSRF令牌進行比較。如果值不匹配,則應拒絕HTTP請求。
這項工作的關鍵是,實際的CSRF令牌應該位于瀏覽器不會自動包含的HTTP請求的一部分中。
ps: 這里可以發現防止重復提交和思想非常類似,甚至是驗證碼也是類似的思路。不過要確保這個信息不會被自動放到請求之中。
例如,在HTTP參數或HTTP標頭中要求實際的CSRF令牌將防止CSRF攻擊。在cookie中要求實際CSRF令牌不起作用,因為瀏覽器會自動將cookie包含在HTTP請求中。
我們可以放寬期望,僅對每個更新應用程序狀態的HTTP請求僅要求實際的CSRF令牌。
為此,我們的應用程序必須確保安全的HTTP方法是冪等的。因為我們希望允許使用外部站點的鏈接來鏈接到我們的網站,所以這提高了可用性。
此外,我們不想在HTTP GET中包含隨機令牌,因為這可能導致令牌泄漏。
讓我們看一下使用同步令牌模式時示例將如何變化。
假設實際的CSRF令牌必須位于名為_csrf的HTTP參數中。
我們應用程序的轉帳表單如下:
<form method="post"
action="/transfer">
<input type="hidden"
name="_csrf"
value="4bfd1575-3ad1-4d21-96c7-4ef2d9f86721"/>
<input type="text"
name="amount"/>
<input type="text"
name="routingNumber"/>
<input type="hidden"
name="account"/>
<input type="submit"
value="Transfer"/>
</form>
現在,該表單包含具有CSRF令牌值的隱藏輸入。
外部站點無法讀取CSRF令牌,因為相同的來源策略可確保惡意站點無法讀取響應。
相應的HTTP匯款請求如下所示:
POST /transfer HTTP/1.1
Host: bank.example.com
Cookie: JSESSIONID=randomid
Content-Type: application/x-www-form-urlencoded
amount=100.00&routingNumber=1234&account=9876&_csrf=4bfd1575-3ad1-4d21-96c7-4ef2d9f86721
您會注意到,HTTP請求現在包含帶有安全隨機值的_csrf參數。
惡意網站將無法為_csrf參數提供正確的值(必須在邪惡網站上明確提供),并且當服務器將實際CSRF令牌與預期CSRF令牌進行比較時,傳輸將失敗。
SameSite屬性
防止CSRF攻擊的一種新興方法是在cookie上指定SameSite屬性。
設置cookie時,服務器可以指定SameSite屬性,以指示從外部站點發出時不應發送該cookie。
Spring Security不直接控制會話cookie的創建,因此它不提供對SameSite屬性的支持。
Spring Session在基于servlet的應用程序中為SameSite屬性提供支持。
Spring Framework的CookieWebSessionIdResolver為基于WebFlux的應用程序中的SameSite屬性提供了開箱即用的支持。
例子
一個示例,帶有SameSite屬性的HTTP響應標頭可能類似于:
Set-Cookie: JSESSIONID=randomid; Domain=bank.example.com; Secure; HttpOnly; SameSite=Lax
屬性值介紹
SameSite屬性的有效值為:
Strict-指定后,來自同一站點的任何請求都將包含cookie。否則,cookie將不會包含在HTTP請求中。
Lax-當來自同一個站點的特定cookie將被發送,或者當請求來自頂層導航且方法是冪等時,將被發送。否則,cookie將不會包含在HTTP請求中。
真實案例
讓我們看一下如何使用SameSite屬性保護我們的示例。
銀行應用程序可以通過在會話cookie上指定SameSite屬性來防御CSRF。
在會話cookie上設置SameSite屬性后,瀏覽器將繼續發送JSESSIONID cookie和來自銀行網站的請求。但是,瀏覽器將不再發送帶有來自邪惡網站的傳輸請求的JSESSIONID cookie。由于會話不再存在于來自邪惡網站的傳輸請求中,因此可以保護應用程序免受CSRF攻擊。
注意范圍
使用SameSite屬性防御CSRF攻擊時,應注意一些重要注意事項。
(1)將SameSite屬性設置為Strict可以提供更強的防御能力,但會使用戶感到困惑。
考慮一個保持登錄到托管在https://social.example.com上的社交媒體網站的用戶。用戶在https://email.example.org上收到一封電子郵件,其中包含指向社交媒體網站的鏈接。
如果用戶單擊鏈接,則他們理所當然地希望能夠通過社交媒體站點進行身份驗證。
但是,如果SameSite屬性為Strict,則不會發送cookie,因此不會對用戶進行身份驗證。
另一個明顯的考慮因素是,為了使SameSite屬性能夠保護用戶,瀏覽器必須支持SameSite屬性。
(2)大多數現代瀏覽器都支持SameSite屬性。
但是,可能仍未使用較舊的瀏覽器。
因此,通常建議將SameSite屬性用作深度防御,而不是針對CSRF攻擊的唯一防護。
何時使用CSRF保護
什么時候應該使用CSRF保護?
我們的建議是對普通用戶可能由瀏覽器處理的任何請求使用CSRF保護。
如果僅創建非瀏覽器客戶端使用的服務,則可能需要禁用CSRF保護。
CSRF保護和JSON
一個常見的問題是“我需要保護由JavaScript發出的JSON請求嗎?”
簡短的答案是,這看情況。
但是,您必須非常小心,因為有些CSRF漏洞會影響JSON請求。
例子
例如,惡意用戶可以使用以下格式使用JSON創建CSRF:
<form action="https://bank.example.com/transfer" method="post" enctype="text/plain">
<input name='{"amount":100,"routingNumber":"evilsRoutingNumber","account":"evilsAccountNumber", "ignore_me":"' value='test"}' type='hidden'>
<input type="submit"
value="Win Money!"/>
</form>
這將產生以下JSON結構
{
"amount": 100,
"routingNumber": "evilsRoutingNumber",
"account": "evilsAccountNumber",
"ignore_me": "=test"
}
如果應用程序未驗證Content-Type,則該應用程序將被暴露。
根據設置的不同,仍然可以通過更新URL后綴以.json結尾的方式來利用驗證Content-Type的Spring MVC應用程序,如下所示:
<form action="https://bank.example.com/transfer.json" method="post" enctype="text/plain">
<input name='{"amount":100,"routingNumber":"evilsRoutingNumber","account":"evilsAccountNumber", "ignore_me":"' value='test"}' type='hidden'>
<input type="submit"
value="Win Money!"/>
</form>
CSRF和無狀態瀏覽器應用程序
如果我的應用程序是無狀態的怎么辦?
這并不一定意味著您受到保護。
實際上,如果用戶不需要針對給定請求在Web瀏覽器中執行任何操作,則他們可能仍然容易受到CSRF攻擊。
例如,考慮一個使用自定義cookie而不是JSESSIONID的應用程序,其中包含其中的所有狀態用于身份驗證。
進行CSRF攻擊后,自定義cookie將與請求一起發送,其方式與在前面的示例中發送JSESSIONID cookie相同。
此應用程序容易受到CSRF攻擊。
使用基本身份驗證的應用程序也容易受到CSRF攻擊。
該應用程序容易受到攻擊,因為瀏覽器將以與前面示例中發送JSESSIONID cookie相同的方式在任何請求中自動包含用戶名和密碼。
CSRF注意事項
實施針對CSRF攻擊的防護時,需要考慮一些特殊注意事項。
登錄
為了防止偽造登錄請求,應保護HTTP請求中的登錄免受CSRF攻擊。
必須防止偽造登錄請求,以使惡意用戶無法讀取受害者的敏感信息。
攻擊執行如下:
- 惡意用戶使用惡意用戶的憑據執行CSRF登錄。現在,將受害者驗證為惡意用戶。
- 然后,惡意用戶欺騙受害者訪問受感染的網站并輸入敏感信息
- 該信息與惡意用戶的帳戶相關聯,因此惡意用戶可以使用自己的憑據登錄并查看vicitim的敏感信息
確保保護HTTP請求不受CSRF攻擊的可能原因是,用戶可能會遇到會話超時,從而導致請求被拒絕。會話超時對于不需要登錄就需要會話的用戶來說是令人驚訝的。
登出
為了防止偽造注銷請求,應該保護注銷HTTP請求免受CSRF攻擊。
必須防止偽造注銷請求,以便惡意用戶無法讀取受害者的敏感信息。
確保注銷HTTP請求免受CSRF攻擊的可能麻煩在于,用戶可能會遇到會話超時,從而導致請求被拒絕。
會話超時對于不希望需要會話才能注銷的用戶來說是令人驚訝的
CSRF和會話超時
通常,預期的CSRF令牌存儲在會話中。這意味著,會話期滿后,服務器將不會找到預期的CSRF令牌并拒絕HTTP請求。
有很多選項可以解決超時問題,每個選項都需要權衡取舍。
減輕超時的最佳方法是使用JavaScript在表單提交時請求CSRF令牌。然后使用CSRF令牌更新該表單并提交。
另一個選擇是使用一些JavaScript,讓用戶知道他們的會話即將到期。用戶可以單擊按鈕以繼續并刷新會話。
最后,預期的CSRF令牌可以存儲在cookie中。這允許預期的CSRF令牌超過會話壽命。
有人可能會問為什么默認情況下預期的CSRF令牌沒有存儲在Cookie中。
這是因為存在已知的漏洞利用,其中另一個域可以設置標頭(例如,用于指定cookie)。這與出現標題X-Requested-With時Ruby on Rails不再跳過CSRF檢查的原因相同。請參閱此webappsec.org線程以獲取有關如何執行漏洞利用的詳細信息。
另一個缺點是,通過刪除狀態(即超時),您將失去在令牌遭到破壞時強制使令牌無效的能力。
分段(文件上傳)
保護分段請求(文件上傳)免受CSRF攻擊會導致雞和蛋的問題。
為了防止發生CSRF攻擊,必須讀取HTTP請求的正文以獲得實際的CSRF令牌。
但是,讀取正文表示文件將被上傳,這意味著外部站點可以上傳文件。
將CSRF保護與 multipart/form-data 一起使用有兩種選擇。
每個選項都有其取舍。
- 將CSRF令牌放入 body
- 將CSRF令牌放入URL
在將Spring Security的CSRF保護與分段文件上傳集成之前,請確保您可以先上傳而無需CSRF保護。
- 將CSRF令牌放入 body
第一種選擇是在請求正文中包含實際的CSRF令牌。通過將CSRF令牌放入正文中,將在執行授權之前讀取正文。這意味著任何人都可以在您的服務器上放置臨時文件。
但是,只有授權用戶才能提交由您的應用程序處理的文件。
通常,這是推薦的方法,因為臨時文件上傳對大多數服務器的影響可以忽略不計。
- 在網址中包含CSRF令牌
如果不允許未經授權的用戶上傳臨時文件,則可以選擇將預期的CSRF令牌作為查詢參數包括在表單的action屬性中。
這種方法的缺點是查詢參數可能會泄漏。
更一般而言,將敏感數據放置在正文或標題中以確保不泄漏是最佳實踐。
- HiddenHttpMethodFilter
在某些應用程序中,可以使用form參數來覆蓋HTTP方法。
例如,下面的 form 可用于將HTTP方法視為刪除而不是 post。
<form action="/process"
method="post">
<!-- ... -->
<input type="hidden"
name="_method"
value="delete"/>
</form>
覆蓋HTTP方法在過濾器中進行。 該過濾器必須放在Spring Security支持之前。
請注意,覆蓋僅發生在帖子上,因此實際上不太可能引起任何實際問題。
但是,仍然最好的方法是確保將其放置在Spring Security的過濾器之前。
小結
希望本文對你有所幫助,如果喜歡,歡迎點贊收藏轉發一波。
我是老馬,期待與你的下次相遇。