CORS
Same-Origin Policy
瀏覽器預設安全機制:不同來源(Origin)的頁面無法存取彼此資源。
Origin = scheme + host + port
| 比較來源 | 與 http://app.com 比較 | 結果 |
|---|---|---|
http://app.com/api | 路徑不同,Origin 相同 | ✅ 允許 |
https://app.com | scheme 不同 | ❌ 跨源 |
http://app.com:8080 | port 不同 | ❌ 跨源 |
http://api.com | host 不同 | ❌ 跨源 |
CORS(Cross-Origin Resource Sharing)讓伺服器透過 HTTP Header 宣告允許的來源,瀏覽器據此決定是否放行跨源請求。
CORS 機制
Simple Request
條件:GET / POST / HEAD,且僅使用簡單 Header(Content-Type: text/plain 等)。瀏覽器直接送請求,伺服器回應中附帶 CORS Header。
sequenceDiagram
participant B as Browser
participant A as API (api.com)
B->>A: GET /data
Note over B,A: Origin: app.com
A->>B: 200 OK
Note over A,B: Access-Control-Allow-Origin: app.com
Note over B: Origin 匹配 → 允許讀取回應
Preflight Request
條件:PUT / DELETE、自訂 Header、Content-Type: application/json 等非簡單請求。瀏覽器自動先送 OPTIONS 確認伺服器是否允許,通過後才送實際請求。
sequenceDiagram
participant B as Browser
participant A as API (api.com)
B->>A: OPTIONS /data
Note over B,A: Origin: app.com<br/>Access-Control-Request-Method: DELETE
A->>B: 204 No Content
Note over A,B: Access-Control-Allow-Origin: app.com<br/>Access-Control-Allow-Methods: DELETE
Note over B: Preflight 通過 → 送實際請求
B->>A: DELETE /data
Note over B,A: Origin: app.com
A->>B: 200 OK
關鍵 Headers
| 方向 | Header | 說明 |
|---|---|---|
| Request | Origin | 請求來源 |
| Request | Access-Control-Request-Method | Preflight 宣告實際方法 |
| Request | Access-Control-Request-Headers | Preflight 宣告自訂 Header |
| Response | Access-Control-Allow-Origin | 允許的 Origin(* 或具體值) |
| Response | Access-Control-Allow-Methods | 允許的 HTTP 方法 |
| Response | Access-Control-Allow-Headers | 允許的請求 Header |
| Response | Access-Control-Allow-Credentials | 是否允許攜帶 Cookie |
| Response | Access-Control-Max-Age | Preflight 結果快取秒數 |
Access-Control-Allow-Origin: * 無法與 Access-Control-Allow-Credentials: true 同時使用。需攜帶 Cookie 時,必須指定具體的 Origin。CSRF 攻擊與防禦
CSRF 攻擊流程
CSRF(Cross-Site Request Forgery):惡意網站誘使已登入使用者的瀏覽器,向目標網站發送非預期請求。
sequenceDiagram
participant U as User
participant E as Evil (evil.com)
participant BK as Bank (bank.com)
U->>BK: POST /login
BK->>U: Set-Cookie: session=abc
U->>E: 瀏覽 evil.com
E->>U: HTML 含隱藏表單 (action=bank.com/transfer)
U->>BK: POST /transfer(Cookie 自動附加)
BK->>U: 200 OK — 攻擊成功
關鍵:HTML form submit 是 Simple Request,不觸發 CORS Preflight,Cookie 仍由瀏覽器自動附帶。
CORS 如何阻擋 CSRF
對於使用 fetch / XMLHttpRequest 發起的跨源 API 請求,CORS 可有效阻擋:
sequenceDiagram
participant E as Evil (evil.com)
participant B as Browser
participant BK as Bank API (bank.com)
E->>B: fetch('bank.com/transfer', {method: 'POST'})
B->>BK: OPTIONS /transfer
Note over B,BK: Origin: evil.com
BK->>B: Access-Control-Allow-Origin: bank.com
Note over B: evil.com ≠ bank.com → 阻擋請求
B->>E: ❌ CORS policy blocked
其他防禦手段
| 方式 | 防禦範圍 |
|---|---|
| CSRF Token | Form + API |
| SameSite Cookie | Form + API |
| Double Submit Cookie | API(stateless 架構) |
| Origin / Referer 驗證 | 輔助手段 |
CSRF Token
伺服器產生隨機 token 存入 session,回傳給前端。攻擊者在 evil.com 無法讀取 bank.com 的 HTML/response(SOP 擋住),故無法取得 token。
token 放置方式依請求類型而異:
| 請求類型 | token 位置 |
|---|---|
| HTML form POST | <input type="hidden" name="csrf_token" value="xyz"> → 送進 form body |
| JSON API(fetch) | X-CSRF-Token: xyz custom request header |
sequenceDiagram
participant U as User (app.com)
participant S as Server
U->>S: GET /form
S->>U: HTML (input type=hidden csrf_token=xyz)
Note over S: Session 存 xyz
U->>S: POST /transfer
Note over U,S: form body: amount=100 & csrf_token=xyz
S->>S: 比對 Session token == form body token ✓
S->>U: 200 OK
SameSite Cookie
後端 Set-Cookie 時設定 SameSite 屬性,瀏覽器自動決定跨站請求是否附帶 cookie,無需前端介入。
| 值 | 跨站 GET 導航 | 跨站 POST | 說明 |
|---|---|---|---|
Strict | ❌ | ❌ | 完全不帶,最嚴格 |
Lax | ✅ | ❌ | 現代瀏覽器預設值 |
None | ✅ | ✅ | 必須加 Secure |
sequenceDiagram
participant U as User
participant E as Evil (evil.com)
participant BK as Bank (bank.com)
U->>BK: POST /login
BK->>U: 200 OK
Note over BK,U: Set-Cookie: session=abc SameSite=Lax
U->>E: 瀏覽 evil.com
E->>U: HTML 含隱藏表單
U->>BK: POST /transfer
Note over U,BK: Cookie 未附加 (SameSite=Lax 阻擋跨站 POST)
BK->>U: 401 Unauthorized
Double Submit Cookie
適用於 stateless / 無 server-side session 架構(純 JWT API)。利用 SOP 讓攻擊者無法讀取 cookie 值,從而無法複製到 header。
sequenceDiagram
participant B as Browser (app.com)
participant S as Server
B->>S: GET /page
S->>B: 200 OK
Note over S,B: Set-Cookie: csrf=xyz (非 HttpOnly)
Note over B: JS 讀取 csrf cookie 值
B->>S: POST /transfer
Note over B,S: Header: X-CSRF-Token: xyz<br/>Cookie: csrf=xyz
S->>S: 比對 Header == Cookie ✓
S->>B: 200 OK
evil.com 的 JS 無法讀 bank.com 的 cookie(SOP),所以無法在 header 填入正確值。
SameSite=Lax 普及後,Double Submit Cookie 多作為 legacy 相容手段,新專案優先考慮 CSRF Token 或 SameSite。