CSRF 與 CORS

是什麼?

ℹ️同源政策(Same-Origin Policy)

瀏覽器預設禁止跨來源請求。CORS 是一種「有條件放寬」同源政策的標準。Origin 由 Protocol + Host + Port 三者組成。

核心觀念

CSRF 攻擊原理

  1. 使用者登入 bank.com,瀏覽器儲存 Session Cookie
  2. 使用者造訪惡意網站 evil.com
  3. evil.com 的頁面包含一個自動提交的表單,目標是 bank.com/transfer
  4. 瀏覽器自動帶上 bank.com 的 Cookie,銀行以為是使用者本人操作

CSRF 防禦方式

| 方式 | 原理 | 推薦度 | |------|------|--------| | CSRF Token | Server 產生隨機 token,表單提交時一起送回驗證 | 高 | | SameSite Cookie | 設定 SameSite=StrictLax,阻止跨站攜帶 Cookie | 高 | | Double Submit Cookie | Token 同時放在 Cookie 和 Header,server 比對兩者 | 中 | | Origin/Referer 檢查 | 驗證請求來源是否合法 | 低(可被繞過) |

CORS 關鍵 Header

| Header | 方向 | 說明 | |--------|------|------| | Origin | Request | 瀏覽器自動帶上,標示請求來源 | | Access-Control-Allow-Origin | Response | 允許的來源,不可在需要憑證時設為 * | | Access-Control-Allow-Methods | Response | 允許的 HTTP 方法 | | Access-Control-Allow-Headers | Response | 允許的自訂 Header | | Access-Control-Allow-Credentials | Response | 是否允許攜帶 Cookie |

常見誤區

⚠️常犯錯誤

  • 設定 Access-Control-Allow-Origin: * 同時又設 Allow-Credentials: true(瀏覽器會直接拒絕)
  • 以為 CORS 能防 CSRF(CORS 只管讀取回應,不管發送請求)
  • 以為只有 POST 需要防 CSRF(GET 請求如果有副作用也需要防)
  • 把 CORS 當成 server 端的安全機制(CORS 是瀏覽器執行的,API client 不受限制)

執行流程

1

瀏覽器發出跨域請求

如果是 Simple Request 直接發送,否則先發 Preflight

2

Preflight(OPTIONS)

瀏覽器自動發送 OPTIONS 請求詢問 server 是否允許

3

Server 回應 CORS Header

回傳 Allow-Origin、Allow-Methods 等 header

4

瀏覽器檢查

比對 Origin 與 server 回應,決定是否放行

5

實際請求

Preflight 通過後才發送實際的 GET/POST 請求

流程解讀:CORS 的 Preflight 機制是瀏覽器自動執行的。當請求不符合 Simple Request 條件(例如使用自訂 Header、Content-Type 非表單格式),瀏覽器會先發一個 OPTIONS 請求「打探」server 的態度。只有 server 明確回應允許,瀏覽器才會發出實際請求。這整個過程對 JavaScript 程式碼是透明的。

程式碼範例

C# 版本

csharp
// ASP.NET Core — CSRF 防護(內建 Antiforgery)
builder.Services.AddAntiforgery(options =>
{
    options.HeaderName = "X-CSRF-TOKEN";
    options.Cookie.SameSite = SameSiteMode.Strict;
    options.Cookie.SecurePolicy = CookieSecurePolicy.Always;
});
 
// Controller 中使用
[HttpPost]
[ValidateAntiForgeryToken]
public IActionResult Transfer(TransferRequest request)
{
    // CSRF token 已自動驗證
    return Ok();
}
 
// CORS 設定
builder.Services.AddCors(options =>
{
    options.AddPolicy("Production", policy =>
    {
        policy.WithOrigins("https://myapp.com", "https://admin.myapp.com")
              .WithMethods("GET", "POST", "PUT", "DELETE")
              .WithHeaders("Authorization", "Content-Type")
              .AllowCredentials();
    });
});

TypeScript 版本

typescript
import express from "express";
import cors from "cors";
import csrf from "csurf";
 
const app = express();
 
// CORS 設定
app.use(cors({
  origin: ["https://myapp.com", "https://admin.myapp.com"],
  methods: ["GET", "POST", "PUT", "DELETE"],
  allowedHeaders: ["Authorization", "Content-Type", "X-CSRF-Token"],
  credentials: true,
}));
 
// CSRF 防護
const csrfProtection = csrf({ cookie: { sameSite: "strict", secure: true } });
 
app.get("/api/csrf-token", csrfProtection, (req, res) => {
  res.json({ token: req.csrfToken() });
});
 
app.post("/api/transfer", csrfProtection, (req, res) => {
  // CSRF token 已驗證
  res.json({ success: true });
});

Python 版本

python
from flask import Flask
from flask_cors import CORS
from flask_wtf.csrf import CSRFProtect
 
app = Flask(__name__)
app.config["SECRET_KEY"] = "secure-random-key"
 
# CORS 設定
CORS(app, origins=["https://myapp.com"],
     supports_credentials=True,
     methods=["GET", "POST", "PUT", "DELETE"])
 
# CSRF 防護
csrf = CSRFProtect(app)
 
@app.route("/api/transfer", methods=["POST"])
def transfer():
    # Flask-WTF 自動驗證 CSRF token
    return {"success": True}
 
# API endpoint 如果用 token-based auth 可以豁免 CSRF
@csrf.exempt
@app.route("/api/v1/data", methods=["POST"])
def api_data():
    # Bearer token auth 不受 CSRF 影響
    return {"data": "ok"}

結構圖

Browser (Same-Origin Policy)
same-origin OK
myapp.com (Legitimate)
evil.com (Attacker)
triggers request
API Server
returns policy
CORS Headers
CSRF Token

圖中 Browser 執行同源政策,myapp.com 的請求直接放行。當 evil.com 企圖透過 Browser 發送跨域請求時,Browser 會先送 Preflight OPTIONS 詢問 Server。Server 透過 CORS Headers 回傳允許清單,Browser 據此決定是否放行。即使 CORS 放行(例如 Simple Request),Server 端的 CSRF Token 驗證仍然是最後一道防線。

面試常見問題

Q: CORS 能防止 CSRF 嗎?

A: 不能。CORS 控制的是瀏覽器是否允許 JavaScript 讀取跨域回應,但不阻止請求的發送。CSRF 攻擊只需要「發送」請求(例如表單提交),不需要讀取回應。防 CSRF 必須用 CSRF Token 或 SameSite Cookie。

Q: 為什麼 SameSite=Lax 是現代瀏覽器的預設值?

A: Lax 在大多數情況下阻止跨站攜帶 Cookie(POST、iframe、AJAX),但允許頂層導航的 GET 請求攜帶(例如從外部連結點進來仍能保持登入狀態)。這在安全性和使用者體驗之間取得了平衡。

Q: Preflight 請求的觸發條件是什麼?

A: 不符合 Simple Request 條件的跨域請求都會觸發 Preflight。Simple Request 的條件:方法限 GET/HEAD/POST,Content-Type 限 application/x-www-form-urlencodedmultipart/form-datatext/plain,不能有自訂 Header。

理解測驗

🤔 CSRF 攻擊為什麼能成功?

🤔 以下哪個 CORS 設定是錯誤的?

🤔 以下哪種方式最能有效防禦 CSRF?

重點整理

💡一句話記住

CSRF 是「借刀殺人」,CORS 是「門禁管制」。 口訣:「CSRF 防偽造要 Token,CORS 防讀取靠 Header」

| 概念 | 說明 | |------|------| | CSRF | 攻擊者利用使用者的 Cookie 發送偽造請求 | | CSRF Token | Server 產生的隨機 token,驗證請求來源合法性 | | SameSite Cookie | 瀏覽器層級阻止跨站攜帶 Cookie | | CORS | 瀏覽器機制,控制跨域資源存取 | | Preflight | 瀏覽器自動發送 OPTIONS 請求確認是否允許跨域 |

你可能也想看

Authentication SecuritySecrets Management

按 ← → 鍵切換課程