帶有無狀態 REST API 的 CSRF
- Rest
- Spring
1. 概述
本文將通過不同的案例來確定無狀態 REST API 是否容易受到 CSRF 攻擊,如果是,則如何保護它免受攻擊。
2. REST API 是否需要 CSRF 保護?
首先,我們可以在我們的專用指南中找到 CSRF 攻擊的示例。
現在,在閱讀本指南時,我們可能會認為無狀態 REST API 不會受到這種攻擊的影響,因為在服務器端沒有要竊取的會話。
讓我們舉一個典型的例子:一個 Spring REST API 應用程序和一個 Javascript 客戶端。客戶端使用安全令牌作為憑證(例如 JSESSIONID 或JWT ),REST API 在用戶成功登錄後發出該憑證。
CSRF 漏洞取決於客戶端如何存儲這些憑據並將這些憑據發送到 API 。
讓我們回顧一下不同的選項以及它們將如何影響我們的應用程序漏洞。
我們將舉一個典型的例子:一個 Spring REST API 應用程序和一個 Javascript 客戶端。客戶端使用安全令牌作為憑證(例如 JSESSIONID 或JWT ),REST API 在用戶成功登錄後發出該憑證。
2.1.憑證不保留
從 REST API 檢索到令牌後,我們可以將令牌設置為 JavaScript 全局變量。這會將令牌保存在瀏覽器的內存中,並且僅對當前頁面可用。
這是最安全的方式:CSRF 和 XSS 攻擊總是導致在新頁面上打開客戶端應用程序,無法訪問用於登錄的初始頁面的內存。
但是,我們的用戶每次訪問或刷新頁面時都必須重新登錄。
在移動瀏覽器上,即使瀏覽器進入後台也會發生這種情況,因為系統會清除內存。
這對用戶來說是如此的限制,以至於這個選項很少被實現。
2.2.存儲在瀏覽器存儲中的憑據
我們可以將令牌保存在瀏覽器存儲中——例如,會話存儲。然後,我們的 JavaScript 客戶端可以從中讀取令牌,並在所有 REST 請求中發送帶有此令牌的授權標頭。
這是一種流行的使用方式,例如 JWT:它易於實現並防止攻擊者使用 CSRF 攻擊。實際上,與 cookie 不同的是,瀏覽器存儲變量不會自動發送到服務器。
但是,此實現容易受到 XSS 攻擊:惡意 JavaScript 代碼可以訪問瀏覽器存儲並隨請求一起發送令牌。在這種情況下,我們必須保護我們的應用程序。
2.3.存儲在 Cookie 中的憑據
另一種選擇是使用 cookie 來保存憑據。然後,我們的應用程序的漏洞取決於我們的應用程序如何使用 cookie。
我們可以使用 cookie 僅保留憑據,如 JWT,但不能對用戶進行身份驗證。
我們的 JavaScript 客戶端必須讀取令牌並將其發送到授權標頭中的 API。
在這種情況下,我們的應用程序不容易受到 CSRF 的攻擊:即使 cookie 是通過惡意請求自動發送的,我們的 REST API 也會從授權標頭而不是從 cookie 中讀取憑據。但是, HTTP-only
標誌設置為false
才能讓我們的客戶端讀取 cookie。
但是,通過這樣做,我們的應用程序將容易受到 XSS 攻擊,就像上一節一樣。
另一種方法是驗證來自會話 cookie 的請求,並將HTTP-only
標誌設置為true
。這通常是 Spring Security 為 JSESSIONID cookie 提供的。當然,為了保持我們的 API 無狀態,我們絕不能在服務器端使用會話。
在這種情況下,我們的應用程序像有狀態應用程序一樣容易受到 CSRF 的攻擊:由於 cookie 將隨任何 REST 請求自動發送,因此單擊惡意鏈接可以執行經過身份驗證的操作。
2.4.其他 CSRF 易受攻擊的配置
某些配置不使用安全令牌作為憑據,但也可能容易受到 CSRF 攻擊。
這是HTTP 基本身份驗證、 HTTP 摘要身份驗證和mTLS 的情況。
它們不是很常見,但有相同的缺點:瀏覽器會自動發送任何 HTTP 請求的憑據。在這些情況下,我們必須啟用 CSRF 保護。
3. 在 Spring Boot 中禁用 CSRF 保護
Spring Security 從版本 4 開始默認啟用 CSRF 保護。
如果我們的項目不需要它,我們可以在自定義WebSecurityConfigurerAdapter
禁用它:
@Configuration
public class SpringBootSecurityConfiguration
extends WebSecurityConfigurerAdapter {
@Override
public void configure(HttpSecurity http) throws Exception {
http.csrf().disable();
}
}
4. 使用 REST API 啟用 CSRF 保護
4.1.Spring配置
如果我們的項目需要 CSRF 保護,我們可以通過在自定義WebSecurityConfigurerAdapter
CookieCsrfTokenRepository
來發送帶有 cookie 的 CSRF 令牌。
我們必須將HTTP-only
標誌設置為false
才能從我們的 JavaScript 客戶端檢索它:
@Configuration
public class SpringSecurityConfiguration extends WebSecurityConfigurerAdapter {
@Override
public void configure(HttpSecurity http) throws Exception {
http.csrf().csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse());
}
}
重新啟動應用程序後,我們的請求收到 HTTP 錯誤,這意味著啟用了 CSRF 保護。
我們可以通過將日誌級別調整為 DEBUG 來確認這些錯誤是從CsrfFilter
<logger name="org.springframework.security.web.csrf" level="DEBUG" />
它將顯示:
Invalid CSRF token found for http://...
此外,我們應該在瀏覽器中看到存在新的XSRF-TOKEN
cookie。
讓我們在 REST 控制器中添加幾行,以將信息也寫入我們的 API 日誌:
CsrfToken token = (CsrfToken) request.getAttribute("_csrf");
LOGGER.info("{}={}", token.getHeaderName(), token.getToken());
4.2.客戶端配置
在客戶端應用程序中, XSRF-TOKEN
cookie 在第一次 API 訪問後設置。我們可以使用 JavaScript 正則表達式來檢索它:
const csrfToken = document.cookie.replace(/(?:(?:^|.*;\s*)XSRF-TOKEN\s*\=\s*([^;]*).*$)|^.*$/, '$1');
然後,我們必須將令牌發送到修改 API 狀態的每個 REST 請求:POST、PUT、DELETE 和 PATCH。
Spring 期望在X-XSRF-TOKEN
標頭中接收它。我們可以簡單地使用 JavaScript Fetch
API 設置它:
fetch(url, {
method: 'POST',
body: JSON.stringify({ /* data to send */ }),
headers: { 'X-XSRF-TOKEN': csrfToken },
})
現在,我們可以看到我們的請求正在運行,並且 REST API 日誌中“Invalid CSRF token”
因此,攻擊者將不可能進行 CSRF 攻擊。例如,嘗試從詐騙網站執行相同請求的腳本將收到“Invalid CSRF token”
錯誤。
事實上,如果用戶沒有先訪問過實際的網站,cookie 將不會被設置,請求就會失敗。
5. 結論
在本文中,我們回顧了可能或不可能對 REST API 進行 CSRF 攻擊的不同上下文。
然後,我們學習瞭如何使用 Spring Security 啟用或禁用 CSRF 保護。