加上 Access-Control-Allow-Origin 之後能運作的 API,不一定是安全的。CORS 並不是加上一次就不用管的東西,關鍵在於把許可的對象收緊。本文講解它的原理和安全的設定方法(不會寫出攻擊步驟)。
先講原理
瀏覽器有一條同源政策:其他來源的 JS 不能擅自讀取其他網站的回應。CORS 就是用來把這條規則的例外只對伺服器明確許可的對象打開的機制。
來源(origin)是協定(https)、主機名稱與埠的組合。https://example.com 與 https://api.example.com 主機不同,所以是不同來源;http://example.com 與 https://example.com 協定不同,也是不同來源。
| 回應標頭 | 含義 |
|---|---|
Access-Control-Allow-Origin | 允許哪個來源讀取回應 |
Access-Control-Allow-Credentials | 是否允許帶 Cookie 等認證資訊的通訊 |
Access-Control-Allow-Methods / -Headers | 允許的 HTTP 方法/標頭 |
關鍵在於,CORS 是屬於「放寬限制」那一側的機制。放寬的方式一旦出錯,本不該被讀取的資料就會被第三方讀到。
還有一點很重要:CORS 阻止的是 JavaScript 讀取回應。簡單請求即使沒有 CORS 許可,也會到達伺服器並被處理。使用自訂標頭等情況時,瀏覽器會先用 OPTIONS 請求(預檢)確認,若不被允許就不送出真正的請求。
與相似機制的區別
CORS 常與名稱相近的機制,以及關係密切的漏洞混淆。
| 用語 | 決定什麼 | 由誰執行 |
|---|---|---|
| 同源政策 | 其他來源不能讀取回應(瀏覽器的預設) | 瀏覽器 |
| CORS | 作為上述規則的例外,允許哪些來源讀取 | 伺服器宣告,瀏覽器執行 |
| CSRF | 讓使用者已登入的瀏覽器送出非本意請求的攻擊 | 以 CSRF Token 與 SameSite 防止 |
| CSP | 自己的頁面可以載入哪些指令碼與連線 | 伺服器宣告,瀏覽器執行 |
CORS 關心的是「回應能不能被讀取」;CSRF 關心的是「能不能代替使用者送出請求」。收緊 CORS 並不能防止 CSRF。
哪些設定是危險的
危險的設定(容易犯)
- 把請求的 Origin 原樣反射回去並予以許可
Access-Control-Allow-Origin: *+ 同時允許認證資訊- 把不必要的 API 也大範圍地對其他來源開放
安全的設定
- 白名單(只放可信任的固定來源)
- 其他一律預設拒絕
- 帶認證資訊時不用
*,而指定具體的來源
設定錯誤通常是怎麼發生的
多數設定錯誤,來自想讓 CORS 錯誤消失。典型的經過如下:
- 從前端帶著 Cookie 呼叫 API,出現 CORS 錯誤
- 加上
Access-Control-Allow-Origin: *。但對帶認證資訊的請求,瀏覽器不會讓頁面讀取*的回應,所以錯誤仍在 - 改成把收到的
Origin原樣回傳,錯誤就消失了。同時,任何網站都能讀取已登入的回應
即使有白名單,比對方式草率也會走到同樣的結果。
- 以「結尾是
example.com」比對,也會放行evil-example.com這類不同的網域 - 以「開頭是
https://example.com」比對,也會放行https://example.com.evil.net - 為了開發方便而允許
null來源,也會允許其他網站能製造出的null來源讀取
請以完全一致比對整個來源(協定、主機與埠)。
安全設定的基本原則
採用白名單方式(最重要)
不要不加判斷地反射 Origin
Origin 原樣 echo 到 Access-Control-Allow-Origin。只有與白名單比對一致時才回傳。由於值會隨請求而變,也要送出 Vary: Origin,避免快取把回應混在一起。帶認證資訊時不要用 *
Access-Control-Allow-Origin: * 與 Allow-Credentials: true 同時使用。要指定具體的來源。把開放範圍降到最小
* 只用於不需要登入的公開資料。如何檢查自己的網站
對你的 API 送出一個自稱來自其他來源的請求,看回應標頭。請只對你管理的 API 這樣做。
# 自稱來自未允許的來源,只看回應標頭
curl -s -o /dev/null -D - -H "Origin: https://example.com" https://your-api.example/ | grep -i "^access-control"- 如果得到
Access-Control-Allow-Origin: https://example.com,表示 API 會反射 Origin。請加上白名單比對 - 如果同時出現
Access-Control-Allow-Origin: *與Access-Control-Allow-Credentials: true,請重新檢討設定 - 如果什麼都沒回傳,或只回傳自己前端的來源,就沒問題
用 Origin: null 再做一次同樣的確認。對已確認所有權的網站,網站安全健檢會一併檢查 Origin 反射與認證資訊設定。其他安全標頭可以用安全回應標頭診斷確認。
出處(一手資料)
- MDN Web Docs, "Cross-Origin Resource Sharing (CORS)" — developer.mozilla.org(帶認證資訊的請求不可用 *、預檢的條件、Vary: Origin)
- WHATWG Fetch Standard, "CORS protocol" — fetch.spec.whatwg.org
- OWASP HTML5 Security Cheat Sheet — cheatsheetseries.owasp.org(只允許可信任的網域;不要未經檢查就回傳 Origin 標頭)
- PortSwigger Web Security Academy, "Cross-origin resource sharing (CORS)" — portswigger.net(白名單比對的錯誤、null 來源、CORS 不是 CSRF 防護)
- MITRE CWE-942: Permissive Cross-domain Policy with Untrusted Domains — cwe.mitre.org