在網路安全領域中,瀏覽器會自動帶入 Cookie 的特性雖然為使用者帶來了無縫的登入體驗,但也成了跨站請求偽造(CSRF)攻擊的主要溫床。本文將深入探討 CSRF 的攻擊原理與防禦策略,並提供實際的程式碼範例。
想了解基礎 HTML 標籤結構與語意化細節,可先前往觀看 〈HTML footer 標籤語意化誤區解析:全域頁尾、區段結尾與內容模型規範〉。
一、CSRF 跨站請求偽造的核心原理與攻擊場景
在網路安全領域中,跨站請求偽造(CSRF 或 XSRF)是一種常見的攻擊方式,它允許惡意網站代表無意中的使用者執行非授權操作。當使用者登入信任網站後,瀏覽器在每次發送請求時,會自動在 HTTP 標頭中夾帶該網站的 Cookie 憑證。惡意網站正是利用了這個瀏覽器自動帶入 Cookie 的機制,在使用者不知情的情況下發動攻擊,對網路安全構成了嚴重威脅。
CSRF 攻擊主要是利用了網站對用戶瀏覽器的盲目信任。當用戶登入一個網站 A 後,在不登出的情況下訪問另一個惡意網站 B。網站 B 可以在用戶不知情的情況下,代表用戶向網站 A 發送請求,如提交表單、更改設定等操作,而這些請求會自動包含用戶的認證資訊(如 Cookies),使得網站 A 誤認為是合法用戶的操作。
攻擊示例情境:
假設一個用戶已經登入了銀行網站 A,在此期間,該用戶點擊了一個來自惡意網站 B 的連結。該連結實際上是一個向銀行網站 A 發送轉帳請求的表單。由於用戶的瀏覽器已經包含了登入網站 A 的 Cookie,因此該請求會被網站 A 視為有效請求,從而完成未經授權的轉帳操作。
二、防禦 CSRF 攻擊的 3 大核心策略與規格對照
為了有效防禦 CSRF 攻擊,開發者可透過請求驗證機制、來源檢查與瀏覽器 Cookie 屬性進行多層防護。以下為常見防禦機制的特性對照:
| 防禦策略 | 運作機制與核心原理 | 優點與適用場景 | 限制與注意事項 |
|---|---|---|---|
| Anti-CSRF Token | 伺服器生成唯一的隨機密鑰並嵌入表單或 AJAX 請求中,提交時進行雜湊驗證。 | 防禦力極高,不依賴瀏覽器特性,適用於所有狀態變更的 POST/PUT 請求。 | 需要在前後端架構中額外處理 Token 的傳遞與儲存邏輯。 |
| SameSite Cookie 屬性 | 限制 Cookies 隨跨站請求發送,提供 Strict、Lax 與 None 三種層級設定。 | 瀏覽器原生支援,設定簡易,能有效阻擋多數自動帶入 Cookie 的跨站請求。 | 須注意 Lax 在 GET 請求與頂層導覽下的防禦盲點。 |
| Referer Header 檢查 | 伺服器端檢查請求標頭的來源網域是否符合白名單。 | 實作成本低,可作為輔助驗證防線。 | 部分瀏覽器或隱私模式會遮蔽 Referer,存在被繞過的風險。 |
1. 使用 Anti-CSRF Token
最有效的防禦手段之一是在表單或 AJAX 請求中使用 Anti-CSRF Token。這是一個隨機生成的唯一標識符,伺服器在渲染頁面時生成並嵌入到頁面中,客戶端提交請求時必須包含此 Token,伺服器再進行驗證。
以下為 HTML 表單內嵌 Hidden Token 的實作範例:
<form action="/transfer" method="POST">
<!-- 伺服器動態生成的 Anti-CSRF Token -->
<input type="hidden" name="csrf_token" value="d97a1d1d82d4282282adbf4f23b6b85d">
<label for="amount">轉帳金額:</label>
<input type="text" id="amount" name="amount">
<label for="to_account">收款帳號:</label>
<input type="text" id="to_account" name="to_account">
<button type="submit">確認轉帳</button>
</form>
2. 使用 SameSite Cookie 屬性
設定 Cookies 的 SameSite 屬性可以限制 Cookies 隨跨站請求發送,從而幫助防止 CSRF 攻擊。SameSite 屬性可以設為 Strict、Lax 或 None。
以下為使用 Express.js 設定 Cookie 的 SameSite 屬性實作範例:
const express = require('express');
const app = express();
app.get('/login', (req, res) => {
// 設定含有 SameSite 與 Secure 屬性的 Cookie
res.cookie('session_id', 'xyz123securetoken', {
httpOnly: true,
secure: true, // 僅透過 HTTPS 傳輸
sameSite: 'lax' // 可設為 'strict' 或 'lax'
});
res.send('登入成功並已寫入 Cookie');
});
app.listen(3000, () => {
console.log('Server is running on port 3000');
});
三、實務踩坑排查與防呆設計
在實務專案開發中,許多團隊在導入 Anti-CSRF Token 與 Cookie 設定時,常因分散式架構或屬性誤解而留下資安漏洞,以下整理兩大高頻踩坑情境與對應解法:
1. Token 狀態儲存與分散式架構(Session vs Stateless JWT)
- 踩坑情境與觸發原因:許多開發者在實作 Anti-CSRF Token 時,誤以為只要在前端埋入隨機字串即可。但在現代分散式微服務或負載平衡(Load Balancer)架構下,若 Token 僅存在單一伺服器的記憶體(In-Memory)或傳統 Session 中,當使用者的請求被導向另一台伺服器時就會驗證失敗,導致合約表單提交不斷報錯。
- 實務防呆與排錯解法:應將 Token 綁定於使用者的加密 Session(如 Redis 集中式快取),或者採用「雙重 Cookie 驗證(Double Submit Cookie pattern)」,將 Token 同時寫入前端不可被 JavaScript 讀取的 HttpOnly Cookie 與表單隱藏欄位中進行雜湊比對。
2. SameSite=Lax 在跨站 POST 請求的防禦盲點
- 踩坑情境與觸發原因:開發者常以為設定了
SameSite=Lax就能萬事大吉。然而,Lax在用戶透過「頂層導覽(Top-Level Navigation)」且使用安全 HTTP 方法(如 GET)時允許攜帶 Cookie。若系統的某些重要狀態變更(例如刪除帳號、轉帳)設計成透過GET請求觸發,SameSite=Lax將完全無法阻擋 CSRF 攻擊。 - 實務防呆與排錯解法:嚴格落實 RESTful API 設計規範,所有會變更伺服器狀態(State-Changing)的操作(如 POST、PUT、DELETE)絕對不能使用
GET方法;針對高敏感操作,應強制採用SameSite=Strict或搭配 Anti-CSRF Token 雙重防護。
四、常見問題 (FAQ)
Q1:CSRF 攻擊跟 XSS 攻擊有什麼差別?
- 精煉解答:兩者最大的差別在於「攻擊目標與信任鏈」。XSS(跨網站指令碼)是攻擊者注入惡意 JavaScript 來「直接竊取使用者的 Cookie 或憑證」(信任你的網站);而 CSRF(跨站請求偽造)則是利用瀏覽器「會自動帶入 Cookie」的特性,欺騙受害者的瀏覽器去「冒充使用者發送未授權的合法請求」(信任使用者的瀏覽器)。簡單來說,XSS 是偷你的鑰匙,CSRF 是趁你沒注意時拿你的鑰匙去開門。
Q2:為什麼 SPA 單頁應用(如 React / Vue)搭配 JWT 還會受到 CSRF 攻擊?
- 精煉解答:這取決於你的 JWT 存在哪裡。如果你的 JWT 或 Access Token 是存放在
localStorage中,透過 JavaScript 每次在 Header(如Authorization: Bearer)手動夾帶,因為惡意網站無法讀取跨網域的localStorage,所以天然免疫傳統 CSRF。但如果你的 JWT 是存放在由瀏覽器自動管理的 Cookie 中,當使用者造訪惡意網站並觸發請求時,瀏覽器依然會自動帶上該 Cookie,這時就會受到 CSRF 攻擊。
Q3:SameSite=Strict 與 SameSite=Lax 到底該怎麼選擇?
- 精煉解答:
Strict:最安全,任何跨站請求(包含從外部網站點擊連結導向你的網站)都不會攜帶 Cookie。缺點是使用者點擊外部連結進到你的網站時會變成「未登入」狀態,影響使用者體驗。Lax:多數情況下的平衡選擇。它會阻擋大多數跨站的 POST、PUT、DELETE 請求,但在「頂層導覽(Top-Level Navigation)」且使用安全的 GET 方法(例如使用者點擊外部連結來到你的網站)時允許攜帶 Cookie。建議高敏感系統(如網路銀行)採用Strict,一般網站則可使用Lax並搭配 Anti-CSRF Token。
Q4:如果網站使用 RESTful API 並採用 Bearer Token 驗證,還需要實作 Anti-CSRF 嗎?
- 精煉解答:如果你的 API 架構完全採用 Token-based Authentication(且 Token 是存在
localStorage或記憶體中,並由前端 JavaScript 在每次請求的Authorization標頭中手動帶入),由於瀏覽器不會自動將其附加到跨站請求中,因此不需要實作 Anti-CSRF Token。但若你的 API 是依賴 Session Cookie 或把 JWT 存在 Cookie 中自動傳遞,就必須實作 Anti-CSRF 防禦。
總結
理解並預防 CSRF 攻擊對於保障網路安全至關重要。透過實施有效的安全策略,如使用 Anti-CSRF Token 和配置 SameSite Cookie 屬性,開發者可以顯著減少這種攻擊的風險。隨著技術的發展,網路安全領域的威脅和防禦措施也在不斷進化,持續的學習和適應是保護網站和用戶安全的關鍵。
參考資料與延伸閱讀
- MDN Web Docs:Set-Cookie HTTP 回應標頭與 SameSite 屬性規範
- OWASP Foundation:Cross-Site Request Forgery (CSRF) 防禦導引與 Anti-CSRF Token 實作
- Express.js 官方文件:res.cookie() 方法與 Cookie 屬性設定參數
- MDN Web Docs:Fetch API 跨來源請求與憑證設定(credentials: include)
- RFC 6265:HTTP State Management Mechanism (Cookie 規範與安全限制)
- 延伸閱讀:HTTPOnly Cookie 怎麼設定?新手工程師常見 XSS 防禦實作
- 延伸閱讀:SSL/TLS(HTTPS)憑證爆資安危機——嚴重評級「重度」之後真的是「印度」?!
發表迴響