# CSRF(Cross-Site Request Forgery)筆記
## 一、一句話定義
CSRF(跨站請求偽造)是一種攻擊手法:**攻擊者誘騙已登入的使用者瀏覽器,在使用者不知情的情況下,向另一個網站發送一個「看起來合法」的請求**,藉此冒用使用者的身份執行操作(轉帳、改密碼、發文…)。
關鍵字只有三個:
- **偽造請求**:不是偷帳密,是偷「身份憑證會自動附上」這件事
- **瀏覽器自動附帶 Cookie**:這是攻擊成立的根本原因
- **使用者不知情**:全程使用者只是點了一個連結、看了一張圖
---
## 二、為什麼會發生?(原理)
瀏覽器有一個天生的行為:**同一個網域的 Cookie,會在你發任何請求給那個網域時自動附上**,不管這個請求是從哪個頁面觸發的。
舉例:
1. 你先登入 `bank.com`,瀏覽器拿到 session cookie。
2. 你沒登出,開了另一個分頁去逛 `evil.com`。
3. `evil.com` 頁面裡藏了一段:
```html
<img src="https://bank.com/transfer?to=attacker&amount=10000">
```
4. 瀏覽器看到這個 `<img>` 標籤,會自動對 `bank.com/transfer` 發出 GET 請求。
5. 因為你還在 `bank.com` 的登入狀態,瀏覽器**自動把 session cookie 附上去**。
6. `bank.com` 伺服器看到「有效 cookie + 合法格式的請求」,就真的執行轉帳了。
整個過程你在 `evil.com` 上什麼都沒點(只是圖片載入失敗),錢已經轉走了。
> 攻擊者要的從來不是你的密碼,而是**利用瀏覽器幫他把你的身份憑證自動送出去**。
---
## 三、CSRF 攻擊的必要條件
三個條件缺一不可:
1. **目標網站用 Cookie 做身份驗證**(且沒有其他防護),瀏覽器會自動帶上
2. **目標網站的操作可以用可預測的 URL / 表單完成**(不需要猜隨機值)
3. **使用者當下處於登入狀態**,且瀏覽器沒有阻擋這類跨站請求
如果 API 是用 `Authorization: Bearer <token>` 這種**手動放進 Header** 的驗證方式(不是 Cookie),瀏覽器不會自動附加,CSRF 就打不進來——這也是很多前後端分離系統改用 JWT + Header 後,天然免疫 CSRF 的原因(但要小心別把 token 存進 Cookie 又不設防護)。
---
## 四、常見攻擊型態
| 型態 | 說明 | 範例 |
|---|---|---|
| GET 型 | 用 `<img>`、`<a>`、`<iframe>` 觸發 | `<img src="/transfer?to=x&amt=100">` |
| POST 型 | 用隱藏表單 + 自動送出的 JS | 誘騙頁面載入後自動 `form.submit()` |
| JSON 型(較少見) | 需搭配 CORS 設定錯誤才有機會 | 通常防護較完整,門檻較高 |
POST 型範例(比 GET 更常見於實務攻擊,因為敏感操作通常不會做成 GET):
```html
<form action="https://bank.com/transfer" method="POST" id="f">
<input type="hidden" name="to" value="attacker">
<input type="hidden" name="amount" value="10000">
</form>
<script>document.getElementById('f').submit();</script>
```
---
## 五、防禦機制(由基礎到進階)
### 1. CSRF Token(同步令牌 Synchronizer Token Pattern)— 最經典
伺服器在產生表單/頁面時,塞一個**隨機、不可預測、跟該次 session 綁定**的 token:
```html
<input type="hidden" name="_csrf" value="a8f5f167f44f4964e6c998dee827110c">
```
送出請求時必須帶上這個 token,伺服器驗證 token 是否吻合。攻擊者的 `evil.com` **拿不到這個 token**(因為同源政策 Same-Origin Policy 擋住了他去讀取 `bank.com` 頁面內容),所以偽造的請求驗證會失敗。
這是 Spring Security 預設啟用的機制(`CsrfFilter`),也是你在 Spring MVC 專案中最常遇到的防護。
### 2. SameSite Cookie 屬性 — 現代瀏覽器的第一道防線
在設定 Cookie 時加上 `SameSite` 屬性,直接從瀏覽器層級控制「這個 Cookie 要不要在跨站請求時被帶上」:
```
Set-Cookie: session=abc123; SameSite=Strict; Secure; HttpOnly
```
- `Strict`:完全不允許跨站帶 Cookie(連使用者點外部連結進站的第一個請求都不帶)
- `Lax`(現代瀏覽器預設值):允許「使用者主動點連結」的導覽請求帶 Cookie,但 `<img>`、`<form>` POST、`fetch` 等背景請求不帶
- `None`:完全不限制,必須搭配 `Secure`(僅 HTTPS)
現代主流瀏覽器(Chrome、Edge 等)已把未指定 SameSite 的 Cookie 視為 `Lax`,這讓「純 GET 型」CSRF 攻擊的威脅大幅下降,但**不能完全取代 CSRF token**,因為:
- 舊瀏覽器可能不支援
- `Lax` 仍可能被特定情境繞過(例如某些 top-level navigation 搭配 POST 的邊界案例)
### 3. Double Submit Cookie — 適合無狀態 / 前後端分離架構
不需要伺服器存 session 對照表:
1. 伺服器發一個隨機值,同時放進 **Cookie** 和回應內容(例如 JSON)
2. 前端發請求時,把這個值從 JS 讀出來,放進 **自訂 Header**(例如 `X-CSRF-Token`)
3. 伺服器比對「Cookie 裡的值」跟「Header 裡的值」是否相同
原理:攻擊者可以讓瀏覽器自動帶 Cookie,但**沒辦法讀取或設定跨站的自訂 Header**(同源政策擋住),所以兩者對不上。
### 4. 檢查 Origin / Referer Header — 輔助防禦
伺服器檢查請求的 `Origin` 或 `Referer` 是否等於自己的網域。簡單有效,但不該當唯一防線(部分舊瀏覽器 / 特定情境下 Referer 可能被省略或偽造空間較大)。
### 5. 關鍵操作要求「再次驗證」
轉帳、改密碼、刪帳號等高敏感操作,即使通過上述防護,仍建議加上「輸入密碼確認」或「二次驗證」,作為最後一道防線。
---
## 六、防禦策略選擇(依架構)
| 架構 | 建議防禦 |
|---|---|
| 傳統 Server-side render(Spring MVC + Thymeleaf/JSP) | CSRF Token(Spring Security 內建)+ SameSite=Lax/Strict |
| 前後端分離,Cookie-based session | SameSite=Strict/Lax + Double Submit Cookie |
| 前後端分離,JWT 存於 localStorage + Header 傳遞 | 天然免疫 CSRF(因為不是 Cookie 自動帶),但要注意 XSS 風險換來的 token 竊取 |
| API Gateway / 微服務架構(例如你目前的 Spring Cloud Gateway) | 建議在 Gateway 層統一驗證 Origin/Referer,並確保下游服務的 session cookie 都設好 SameSite,前端呼叫走 Header token 機制 |
---
## 七、常見誤解澄清
- **CSRF ≠ XSS**:XSS 是注入惡意程式碼到你的網站裡執行;CSRF 是利用你「已登入」這件事偽造請求,兩者可以組合攻擊(XSS 可以繞過大部分 CSRF 防護,因為它能在同源環境下讀 token)。
- **CSRF 不需要竊取密碼或 Cookie**:攻擊者完全不需要拿到你的 Cookie 內容,只需要瀏覽器「自動幫他附上」就夠了。
- **GET 請求不該有副作用**:如果你的系統允許用 GET 做「轉帳」「刪除」這類操作,本身就是設計錯誤,CSRF 只是放大了這個問題。
- **裝了 HTTPS 不代表防 CSRF**:HTTPS 防的是傳輸過程被竊聽/竄改,跟「請求是不是使用者本人發起的」是兩回事。
---
## 八、一句話總結
> CSRF 的本質是:**瀏覽器很老實地幫你把身份憑證自動送出去,但它分不出這個請求是不是你自己想發的**。防禦的核心思路,就是想辦法讓「跨站送來的請求」少一樣攻擊者拿不到的東西(token、自訂 Header、SameSite 限制)。