CSRF 與 CORS:以正確方式保護瀏覽器請求
你已經建好了一個擁有簡潔 API 的網頁應用程式,但接著你在日誌中注意到奇怪的請求——或者更糟,安全掃描器因為 CSRF 與 CORS 的錯誤配置而標記了你的網站。這兩個縮寫經常被混為一談,但它們解決的是不同的問題。誤解它們可能會讓你的使用者暴露於風險之中,或是破壞合法的跨來源請求。
在本文中,我們將釐清 CSRF 與 CORS 究竟是什麼、它們如何與瀏覽器安全機制互動,並提供具體步驟,讓你在不破壞功能的前提下保護應用程式。
什麼是 CSRF,為什麼你該在意?
跨站請求偽造(CSRF)是一種攻擊手法,它誘騙使用者的瀏覽器向使用者已通過驗證的網站送出請求。想像你已登入 bank.com 的網路銀行。接著你造訪了一個惡意網站,其中包含像 <img src="https://bank.com/transfer?to=attacker&amount=1000"> 這樣的圖片標籤。你的瀏覽器會自動在請求中帶上你的銀行 cookie,而如果銀行沒有 CSRF 防護,這筆轉帳就會成功執行。
關鍵在於:CSRF 利用的是網站對使用者瀏覽器的信任。攻擊者不需要竊取你的工作階段;他們只需要你的瀏覽器代替你發出請求。
CSRF 攻擊如何運作
要讓 CSRF 攻擊成功,必須滿足三個條件:
- 受害者必須已通過驗證(例如擁有有效的工作階段 cookie)。
- 攻擊者必須知道請求的結構(端點、參數)。
- 請求中不得包含任何攻擊者無法猜測的不可預測參數。
常見的目標是會改變狀態的操作:變更電子郵件、轉帳、發佈內容或修改權限。
什麼是 CORS,為什麼它會存在?
跨來源資源共用(CORS)是一種瀏覽器機制,用來允許或拒絕網頁向與提供該頁面不同的網域發出請求。它是同源政策(SOP)的延伸,後者限制了從某個來源載入的文件或指令碼能如何與另一個來源的資源互動。
如果沒有 CORS,當你已登入時,惡意網站就能使用 JavaScript 讀取你銀行 API 的資料。CORS 讓伺服器能夠明確允許特定的跨來源請求。
同源政策(SOP)
若兩個 URL 的協定、主機與連接埠完全相同,它們就屬於同一個來源。例如,https://example.com/app 與 https://example.com/api 共用同一個來源,但 http://example.com(不同協定)與 https://api.example.com(不同主機)則不是。
SOP 防止來自某個來源的指令碼讀取另一個來源的回應。CORS 透過加入 HTTP 標頭來放寬這項限制,告訴瀏覽器是否允許該請求。
CSRF 與 CORS:主要差異
至關重要的是要理解,CSRF 與 CORS 並非對立的概念;它們處理的是不同的安全問題。CSRF 關注的是防止未經授權的狀態改變請求,而 CORS 關注的是控制哪些來源可以讀取回應。
| 面向 | CSRF | CORS |
|---|---|---|
| 主要目標 | 防止偽造請求 | 控制跨來源讀取 |
| 攻擊向量 | 惡意網站觸發請求 | 惡意網站讀取回應 |
| 防禦機制 | Token、SameSite cookie | HTTP 標頭(Access-Control-*) |
| 瀏覽器強制執行 | 無(伺服器必須驗證) | 有(瀏覽器阻擋讀取) |
如何防禦 CSRF
有數種經過驗證的策略可以緩解 CSRF。你不需要全部採用,但分層防禦是明智之舉。
1. 使用防 CSRF Token
最穩健的防禦是在每個會改變狀態的請求中包含一個獨特且不可預測的 token。伺服器在處理前會驗證該 token。Token 應與使用者的工作階段綁定,且不應暴露於 URL 中(以避免透過 referrer 標頭洩漏)。
// Example: Generating and validating a CSRF token in Node.js/Express
const csrf = require('csurf');
const csrfProtection = csrf({ cookie: true });
app.get('/form', csrfProtection, (req, res) => {
res.render('form', { csrfToken: req.csrfToken() });
});
app.post('/process', csrfProtection, (req, res) => {
// Token is automatically validated
res.send('OK');
});
2. 設定 SameSite Cookie
現代瀏覽器支援 cookie 的 SameSite 屬性。將其設為 Lax 或 Strict 可防止瀏覽器在跨站請求中送出 cookie,這能阻擋許多 CSRF 攻擊。Lax 允許在頂層導覽(例如點擊連結)時送出 cookie,而 Strict 則阻擋所有跨站請求。
Set-Cookie: sessionId=abc123; SameSite=Lax; Secure; HttpOnly
3. 驗證 Origin 與 Referer 標頭
檢查傳入請求的 Origin 或 Referer 標頭。如果它們與你預期的網域不符,就拒絕該請求。這是一個簡單但有效的額外防禦層。
4. 為 AJAX 使用自訂標頭
如果你的 API 是透過 JavaScript 取用,要求一個像 X-Requested-With 這樣的自訂標頭。瀏覽器對自訂標頭會強制執行 CORS 預檢請求,因此來自惡意網站的簡單表單 POST 不會包含它。
正確配置 CORS
CORS 的錯誤配置也可能導致安全問題。最常見的錯誤是將 Access-Control-Allow-Origin: * 與允許憑證同時設定。這種組合會被瀏覽器禁止,但開發人員有時會試圖以不安全的方式繞過它。
CORS 標頭的最佳實務
- 當涉及憑證時,指定確切的來源而非萬用字元。
- 將允許的方法限制為你的 API 實際支援的(例如
GET, POST)。 - 將允許的標頭限制為你的應用程式實際使用的那些。
- 設定
Access-Control-Max-Age來快取預檢回應並減少額外負擔。 - 避免盲目地反射
Origin標頭;應依據白名單進行驗證。
# Example: Nginx configuration for CORS
location /api/ {
if ($http_origin ~* (https://(app|admin)\.example\.com)) {
add_header 'Access-Control-Allow-Origin' $http_origin;
add_header 'Access-Control-Allow-Credentials' 'true';
add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS';
add_header 'Access-Control-Allow-Headers' 'Content-Type, Authorization';
}
if ($request_method = 'OPTIONS') {
return 204;
}
}
請記住,CORS 是由瀏覽器強制執行,而非伺服器。它不能取代身分驗證或授權;它是一種安全地放寬同源政策的方式。
整合運用
保護瀏覽器請求需要採取分層的方式。以下是一份快速檢查清單:
- 使用防 CSRF token 於所有會改變狀態的操作。
- 設定 SameSite cookie 為
Lax或Strict。 - 在伺服器上驗證 Origin/Referer 標頭。
- 精確配置 CORS:將來源加入白名單、限制方法,並避免在涉及憑證時使用萬用字元。
- 定期測試你的應用程式,使用安全掃描器與手動檢查。
透過理解 CSRF 與 CORS 各自不同的角色,你可以避免常見的陷阱,並打造更安全的網頁應用程式。
常見問題
CORS 能防止 CSRF 攻擊嗎?
不能,CORS 無法防止 CSRF。CSRF 攻擊不依賴讀取回應;它們只是觸發請求。CORS 控制哪些來源可以讀取回應,但它不會阻止請求被送出。要防止 CSRF,你需要 token、SameSite cookie 或來源驗證。
將 Access-Control-Allow-Origin 設為 '*' 安全嗎?
只有在你的 API 不使用憑證(cookie、HTTP 驗證)時,將它設為 '*' 才是安全的。如果涉及憑證,瀏覽器會拒絕該回應。對於需要驗證的 API,請務必指定確切的來源。
如果我在 Authorization 標頭中使用 JWT,還需要 CSRF 防護嗎?
如果你將 JWT 儲存在 cookie 中,你仍然需要 CSRF 防護,因為 cookie 會自動送出。如果你將 JWT 儲存在記憶體中並透過 Authorization 標頭送出,則無需擔心 CSRF,因為攻擊者無法跨來源設定自訂標頭。不過,你必須防範 XSS 以確保 token 的安全。
準備好測試你 API 的 CORS 標頭了嗎?使用我們的 Nginx 日誌分析器 來檢視請求模式並找出可疑的跨來源嘗試。