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 TokenServer 產生隨機 token,表單提交時一起送回驗證高
SameSite Cookie設定 SameSite=Strict 或 Lax,阻止跨站攜帶 Cookie高
Double Submit CookieToken 同時放在 Cookie 和 Header,server 比對兩者中
Origin/Referer 檢查驗證請求來源是否合法低(可被繞過)

CORS 關鍵 Header

Header方向說明
OriginRequest瀏覽器自動帶上,標示請求來源
Access-Control-Allow-OriginResponse允許的來源,不可在需要憑證時設為 *
Access-Control-Allow-MethodsResponse允許的 HTTP 方法
Access-Control-Allow-HeadersResponse允許的自訂 Header
Access-Control-Allow-CredentialsResponse是否允許攜帶 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-urlencoded、multipart/form-data、text/plain,不能有自訂 Header。

理解測驗

🤔 CSRF 攻擊為什麼能成功?

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

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

重點整理

💡一句話記住

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

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

你可能也想看

← Authentication SecuritySecrets Management →

按 ← → 鍵切換課程