跨站請求偽造 (CSRF)

Spring 為防範 跨站請求偽造 (CSRF) 攻擊提供了全面的支援。在以下部分中,我們將探討

文件的這一部分討論了 CSRF 保護的一般主題。有關基於 servletWebFlux 應用程式的 CSRF 保護的具體資訊,請參閱相關部分。

什麼是 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 請求可能看起來像

轉賬 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 攻擊 影響的誠信網站時,這種情況也可能發生。那麼我們如何保護使用者免受此類攻擊呢?

防範 CSRF 攻擊

CSRF 攻擊之所以可能,是因為來自受害者網站的 HTTP 請求和來自攻擊者網站的請求完全相同。這意味著無法拒絕來自惡意網站的請求,只允許來自銀行網站的請求。為了防範 CSRF 攻擊,我們需要確保請求中包含惡意網站無法提供的內容,以便我們可以區分這兩個請求。

Spring 提供了兩種機制來防範 CSRF 攻擊

兩種保護都要求 安全方法是隻讀的

安全方法必須是隻讀的

為了使 任一保護 免受 CSRF 攻擊生效,應用程式必須確保 “安全” HTTP 方法是隻讀的。這意味著使用 HTTP GETHEADOPTIONSTRACE 方法的請求不應更改應用程式的狀態。

同步器令牌模式

防範 CSRF 攻擊的主要且最全面的方法是使用 同步器令牌模式。此解決方案旨在確保每個 HTTP 請求除了我們的會話 cookie 之外,還需要在 HTTP 請求中包含一個安全的隨機生成值,稱為 CSRF 令牌。

當提交 HTTP 請求時,伺服器必須查詢預期的 CSRF 令牌並將其與 HTTP 請求中的實際 CSRF 令牌進行比較。如果值不匹配,則應拒絕 HTTP 請求。

此方法奏效的關鍵在於,實際的 CSRF 令牌應位於 HTTP 請求中不會被瀏覽器自動包含的部分。例如,要求實際 CSRF 令牌位於 HTTP 引數或 HTTP 頭部中將有助於防範 CSRF 攻擊。要求實際 CSRF 令牌位於 cookie 中不起作用,因為 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 響應頭示例可能看起來像

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 攻擊時,有一些重要的注意事項需要注意。

SameSite 屬性設定為 Strict 提供了更強的防禦,但可能會讓使用者感到困惑。考慮一個使用者,他一直登入在 social.example.com 上託管的社交媒體網站。使用者在 email.example.org 收到了包含社交媒體網站連結的電子郵件。如果使用者點選了該連結,他們理所當然地會期望被認證到社交媒體網站。但是,如果 SameSite 屬性是 Strict,則不會發送 cookie,因此使用者將無法進行身份驗證。

另一個明顯的考慮是,為了使 SameSite 屬性保護使用者,瀏覽器必須支援 SameSite 屬性。大多數現代瀏覽器都 支援 SameSite 屬性。但是,仍在使用的舊瀏覽器可能不支援。

因此,我們通常建議將 SameSite 屬性作為深度防禦,而不是唯一防範 CSRF 攻擊的方法。

何時使用 CSRF 保護

何時應該使用 CSRF 保護?我們的建議是對任何普通使用者可以透過瀏覽器處理的請求使用 CSRF 保護。如果你正在建立一個僅供非瀏覽器客戶端使用的服務,你可能希望停用 CSRF 保護。

CSRF 保護和 JSON

一個常見問題是“我是否需要保護 JavaScript 發出的 JSON 請求?”簡短的回答是:視情況而定。但是,你必須非常小心,因為存在可能影響 JSON 請求的 CSRF 漏洞。例如,惡意使用者可以透過使用以下表單建立 帶有 JSON 的 CSRF

帶有 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 結構

帶有 JSON 的 CSRF 請求
{ "amount": 100,
"routingNumber": "evilsRoutingNumber",
"account": "evilsAccountNumber",
"ignore_me": "=test"
}

如果應用程式未驗證 Content-Type 標頭,它將暴露於此漏洞。根據設定,驗證 Content-Type 的 Spring MVC 應用程式仍然可以透過將 URL 字尾更新為以 .json 結尾來利用,如下所示

帶有 JSON 的 CSRF 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 的應用程式,該 cookie 包含所有狀態以進行身份驗證(而不是 JSESSIONID)。當發起 CSRF 攻擊時,自定義 cookie 會像我們之前的示例中傳送 JSESSIONID cookie 的方式一樣隨請求傳送。此應用程式容易受到 CSRF 攻擊。

使用基本認證的應用程式也容易受到 CSRF 攻擊。該應用程式容易受到攻擊,因為瀏覽器會自動在所有請求中包含使用者名稱和密碼,就像我們之前的示例中傳送 JSESSIONID cookie 的方式一樣。

CSRF 考慮事項

在實現防範 CSRF 攻擊時,有一些特殊考慮事項。

登入

為了防範偽造登入請求,登入 HTTP 請求應受到 CSRF 攻擊的保護。保護登入請求免受偽造是必要的,這樣惡意使用者就無法讀取受害者的敏感資訊。攻擊按如下方式執行

  1. 惡意使用者使用惡意使用者的憑據執行 CSRF 登入。受害者現在已以惡意使用者的身份進行身份驗證。

  2. 惡意使用者然後誘騙受害者訪問受損網站並輸入敏感資訊。

  3. 這些資訊與惡意使用者的帳戶相關聯,因此惡意使用者可以使用自己的憑據登入並檢視受害者的敏感資訊。

確保登入 HTTP 請求免受 CSRF 攻擊的一個可能複雜之處是使用者可能會遇到會話超時,導致請求被拒絕。會話超時對不期望需要會話才能登入的使用者來說是令人驚訝的。有關更多資訊,請參閱 CSRF 和會話超時

登出

為了防範偽造登出請求,登出 HTTP 請求應受到 CSRF 攻擊的保護。保護登出請求免受偽造是必要的,這樣惡意使用者就無法讀取受害者的敏感資訊。有關攻擊的詳細資訊,請參閱此部落格文章

確保登出 HTTP 請求受到 CSRF 攻擊保護的一個可能複雜之處是使用者可能會遇到會話超時,導致請求被拒絕。會話超時對不期望擁有會話即可登出的使用者來說是令人驚訝的。有關更多資訊,請參閱 CSRF 和會話超時

CSRF 和會話超時

通常情況下,預期的 CSRF 令牌儲存在會話中。這意味著,一旦會話過期,伺服器將找不到預期的 CSRF 令牌並拒絕 HTTP 請求。有許多選項(每個選項都有權衡)可以解決超時問題

  • 緩解超時的最佳方法是使用 JavaScript 在表單提交時請求 CSRF 令牌。然後用 CSRF 令牌更新表單並提交。

  • 另一個選項是使用一些 JavaScript 來告知使用者會話即將過期。使用者可以單擊按鈕繼續並重新整理會話。

  • 最後,預期的 CSRF 令牌可以儲存在 cookie 中。這使得預期的 CSRF 令牌可以比會話更長久。

    有人可能會問為什麼預期的 CSRF 令牌預設不儲存在 cookie 中。這是因為存在已知漏洞,其中頭部(例如,用於指定 cookie)可以由其他域設定。這就是 Ruby on Rails 不再在存在頭部 X-Requested-With 時跳過 CSRF 檢查 的相同原因。有關如何執行漏洞的詳細資訊,請參閱 此 webappsec.org 執行緒。另一個缺點是,透過刪除狀態(即超時),你會失去在令牌被洩露時強制使其失效的能力。

多部分 (檔案上傳)

保護多部分請求(檔案上傳)免受 CSRF 攻擊會導致一個 先有雞還是先有蛋 的問題。為了防止 CSRF 攻擊發生,必須讀取 HTTP 請求的主體以獲取實際的 CSRF 令牌。但是,讀取主體意味著檔案已上傳,這意味著外部站點可以上傳檔案。

使用 CSRF 保護處理 multipart/form-data 有兩種選擇

每個選項都有其權衡。

在將 Spring Security 的 CSRF 保護與多部分檔案上傳整合之前,你應該首先確保可以在沒有 CSRF 保護的情況下上傳。有關使用 Spring 的多部分表單的更多資訊,請參閱 Spring 參考文件的 1.1.11. 多部分解析器 部分和 MultipartFilter Javadoc

將 CSRF 令牌放在主體中

第一個選項是將實際的 CSRF 令牌包含在請求的主體中。透過將 CSRF 令牌放在主體中,在執行授權之前會讀取主體。這意味著任何人都可以將臨時檔案放在你的伺服器上。但是,只有經過授權的使用者才能提交由你的應用程式處理的檔案。通常,這是推薦的方法,因為臨時檔案上傳對大多數伺服器的影響可以忽略不計。

在 URL 中包含 CSRF 令牌

如果允許未經授權的使用者上傳臨時檔案是不可接受的,另一種方法是將預期的 CSRF 令牌作為查詢引數包含在表單的 action 屬性中。這種方法的缺點是查詢引數可能會洩露。更普遍地,將敏感資料放在主體或頭部中以確保其不被洩露被認為是最佳實踐。你可以在 RFC 2616 第 15.1.3 節 URI 中敏感資訊的編碼 中找到更多資訊。

HiddenHttpMethodFilter

某些應用程式可以使用表單引數來覆蓋 HTTP 方法。例如,以下表單可以將 HTTP 方法視為 delete 而不是 post

CSRF 隱藏 HTTP 方法表單
<form action="/process"
	method="post">
	<!-- ... -->
	<input type="hidden"
		name="_method"
		value="delete"/>
</form>

覆蓋 HTTP 方法發生在過濾器中。該過濾器必須放置在 Spring Security 的支援之前。請注意,覆蓋僅發生在 post 請求上,因此這實際上不太可能導致任何實際問題。但是,仍然最好確保它放置在 Spring Security 的過濾器之前。

© . This site is unofficial and not affiliated with VMware.