# 🔐 CSRF(Cross-Site Request Forgery)完整解析
---
## 🧠 什麼是 CSRF?
**CSRF** 是一種「欺騙使用者瀏覽器」的攻擊方式,讓使用者在 **不知情的情況下觸發對網站的請求**,例如轉帳、修改密碼等。
攻擊者並不直接竊取資料,而是 **利用使用者登入中的狀態**,假冒使用者發送請求給網站伺服器。
---
## 🧪 攻擊示意流程
以「銀行轉帳」為例:
1. 使用者登入網銀(`bank.com`),且保持登入狀態(cookie 儲存中)
2. 使用者瀏覽了一個惡意網站(`evil.com`)
3. 該網站偷偷執行以下 HTML:
```html
<img src="https://bank.com/transfer?to=attacker&amount=10000">
```
4. 瀏覽器會自動帶上 `bank.com` 的 cookie,導致轉帳請求被視為合法
5. 使用者不知不覺中損失錢財
---
## 🧨 為何會成功?
因為:
* 使用者已登入目標網站,瀏覽器有 cookie
* 瀏覽器自動附帶 cookie 發送請求(即使從別的網站觸發)
* 伺服器沒有驗證「這個請求是使用者自己發的」
---
## 🚫 CSRF 攻擊條件
✅ 攻擊可能成功的前提:
* 目標網站使用 **cookie 作為登入憑證**
* 請求會造成「狀態變更」(如 POST、DELETE)
* 伺服器 **未驗證請求來源**
---
## 🛡️ 如何防禦 CSRF?
### ✅ 1. CSRF Token(最常見)
* 每個會改變狀態的請求需帶一個 **隨機產生的 token**
* Token 僅對登入者有效,且不能從別站取得
* 前端會夾帶此 token 在 header 或 body 裡
**Spring Security 範例:**
伺服器端:
```java
http.csrf().csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse());
```
前端傳送時加上:
```http
X-CSRF-TOKEN: <token>
```
---
### ✅ 2. SameSite Cookie 屬性
瀏覽器層防護,限制第三方網站請求時 cookie 是否夾帶:
* `SameSite=Strict`:完全禁止第三方網站帶 cookie(最安全)
* `SameSite=Lax`:允許部分 GET 請求
* `SameSite=None`:允許所有跨站請求(但必須加 `Secure`)
---
### ✅ 3. 驗證 Referer / Origin Header
伺服器檢查來源是否為預期網域:
```java
String origin = request.getHeader("Origin");
if (!"https://yourdomain.com".equals(origin)) {
throw new SecurityException("Invalid request origin");
}
```
缺點:部分老瀏覽器或中間代理可能不附帶這些 header。
---
### ✅ 4. 改用 Token-Based Auth(例如 JWT)
不用 cookie 儲存登入狀態,而改用:
```http
Authorization: Bearer <token>
```
因為這樣的 token **不會自動被瀏覽器附加到請求中**,攻擊者難以偽造。
---
## 🧱 常見錯誤理解
| 誤解 | 正確說明 |
| ------------------ | ------------------------------- |
| CORS 可以防止 CSRF | ❌ CORS 是防止 JS 抓資料,不是防止請求被觸發 |
| CSRF 攻擊只用 GET 方法 | ✅ 主要目標是非 GET 方法,但 GET 若有副作用也有風險 |
| 用 captcha 或登入驗證就夠了 | ❌ 不夠,這防的是 bot,不是瀏覽器自動附 cookie |
---
## 📌 小結一句話
> CSRF 是利用使用者登入狀態進行欺騙請求,最佳防禦是使用 **CSRF token + SameSite cookie + 不信任外部來源請求**。
---
### [CSRF](https://docs.spring.io/spring-security/site/docs/5.0.x/reference/html/csrf.html)
CSRF(跨站請求偽造)是一種網路攻擊,也被稱為XSRF或Sea Surf。它利用已經認證的使用者身份,在使用者毫不知情的情況下,透過使用者的瀏覽器向目標網站發送惡意請求。攻擊者試圖利用目標網站對已經認證使用者的信任,以執行未經使用者授權的操作。
攻擊步驟如下:
1. 使用者在某個網站登錄並獲得身份認證。
2. 攻擊者建立了包含惡意請求的網頁,並將其植入到某個網站或透過其他手段引導使用者訪問。
3. 使用者訪問包含惡意請求的網頁,其中的惡意請求會利用使用者在目標網站的認證狀態。
4. 使用者的瀏覽器在毫不知情的情況下,將惡意請求發送到目標網站。
5. 目標網站因為認為請求是經過認證使用者發起的,可能會執行該請求。
CSRF攻擊的目標是執行一些具有破壞性的操作,例如更改密碼、修改使用者信息、進行金融交易等。為了防範CSRF攻擊,常見的做法是使用CSRF令牌(CSRF Token)機制。伺服器生成一個唯一的令牌並將其嵌入到表單中或者作為請求標頭的一部分,使用者在提交表單或發起請求時,伺服器驗證令牌的有效性,確保請求是合法的。這樣,即使攻擊者成功引導使用者發起請求,由於缺乏有效的CSRF令牌,請求將被伺服器拒絕。
在Spring Security等框架中,開發者可以啟用CSRF防護機制,並在需要的地方使用CSRF令牌來增加應用程式的安全性。
在這個例子中,`csrf().disable()` 被用於禁用CSRF防護。請注意,禁用CSRF防護可能會使你的應用更容易受到CSRF攻擊,因此請確保在禁用時採取其他安全措施。
---
在使用AJAX時,如何在POST請求中包含CSRF令牌(即${_csrf.parameterName}和${_csrf.token})取決於你使用的是什麼庫或框架。以下是一個基本的例子:
假設你正在使用jQuery來進行AJAX請求,可以通過下面的方式將CSRF令牌添加到POST請求中:
```javascript
// 獲取CSRF令牌的值
var csrfParameter = '${_csrf.parameterName}';
var csrfToken = '${_csrf.token}';
// 使用jQuery發送POST請求
$.ajax({
url: 'your_url_here',
type: 'POST',
data: {
// 在請求中包含CSRF令牌
csrfParameter: csrfToken,
// 其他請求數據
key1: value1,
key2: value2
},
success: function(response) {
// 處理回應
}
});
```
請注意,上述程式碼中`${_csrf.parameterName}`和`${_csrf.token}`是Thymeleaf模板引擎的語法,用於從後端獲取CSRF令牌。確保這些值在載入頁面時已經由伺服器端生成並注入到頁面中。
如果你使用其他JavaScript庫或框架進行AJAX請求,請參考相應的文件查找如何在POST請求中包含CSRF令牌的方法。
---
以下是如何在 Spring Boot + Spring Security 中**啟用、設定與測試 CSRF 保護**的完整說明與範例。
---
## 🔐 1. CSRF 保護的預設行為(Spring Security)
Spring Security 在 **預設情況下已經啟用 CSRF 保護**,適用於:
* 非 GET / HEAD / OPTIONS 請求(如 POST、PUT、DELETE)
* 表單提交或 AJAX 請求
如果你用表單或 REST API 發出修改資料的請求,卻沒有帶上 CSRF Token,會看到:
```
403 Forbidden: Invalid CSRF Token
```
---
## ✅ 2. 表單方式提交(HTML 表單)
Spring Security 自動會在 Thymeleaf 中加入 CSRF token(如果你用的是 Spring MVC + Thymeleaf):
```html
<form action="/transfer" method="POST">
<input type="hidden" name="_csrf" value="${_csrf.token}" />
<!-- 其他欄位 -->
</form>
```
或者用 Thymeleaf 的標籤:
```html
<form th:action="@{/transfer}" method="post" th:object="${...}">
<input type="hidden" th:name="${_csrf.parameterName}" th:value="${_csrf.token}" />
</form>
```
---
## 🧪 3. REST API 情境:以 Ajax / Fetch 發送 CSRF Token
若你用 JavaScript 發送 POST/PUT 等請求,就要手動附上 CSRF token。
### ✅ 從 Cookie 或 meta tag 讀取 CSRF Token 並加進 header:
```javascript
const token = document.querySelector('meta[name="_csrf"]').getAttribute('content');
const header = document.querySelector('meta[name="_csrf_header"]').getAttribute('content');
fetch('/api/update', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
[header]: token
},
body: JSON.stringify({ key: 'value' })
});
```
在 HTML 中放上 CSRF meta 標籤:
```html
<meta name="_csrf" content="${_csrf.token}">
<meta name="_csrf_header" content="${_csrf.headerName}">
```
---
## ⚙️ 4. Spring Security 配置範例
```java
@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
.csrf() // 預設開啟,可進一步客製
.and()
.authorizeHttpRequests(auth -> auth
.requestMatchers("/api/public/**").permitAll()
.anyRequest().authenticated()
);
return http.build();
}
}
```
> 你也可以用 `.csrf().disable()` 暫時關掉 CSRF,但 **請勿在正式環境這樣做!**
---
## 🛠 5. 手動提供 CSRF Token(進階)
若你需要將 CSRF token 替換成 cookie(SPA 常見),可以用:
```java
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
.csrf(csrf -> csrf
.csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse())
);
return http.build();
}
```
這樣 Spring 會自動在每次回應中夾帶一個 `XSRF-TOKEN` 的 Cookie。
---
## ✅ 總結
| 項目 | 建議做法 |
| ------------- | ------------------------------------------ |
| 表單提交 | 使用隱藏欄位 `name="_csrf"` |
| JavaScript 請求 | 將 token 放進 header(`X-CSRF-TOKEN`) |
| SPA 架構 | 可使用 `CookieCsrfTokenRepository` 自動發送 Token |
| 測試是否有效 | 發送 POST 請求時不帶 token → 應該收到 403 Forbidden |
---