Cross-Origin Resource Sharing(CORS,跨來源資源共用)是一套由瀏覽器執行、由伺服器回應標頭表達授權意圖的協定。它讓受信任的前端網頁能跨 Origin 讀取 API 回應,也可能因為反射任意 Origin、過度寬鬆的 Allowlist、Credential 設定或快取錯誤,讓攻擊者網站在受害者瀏覽器中讀取原本受 Same-Origin Policy 隔離的資料。

先講結論:CORS 控制的是瀏覽器能不能把回應交給前端程式
Same-Origin Policy 預設限制某個 Origin 載入的 JavaScript 讀取另一個 Origin 的回應。CORS 讓伺服器透過 Access-Control-Allow-Origin 等回應標頭,告訴瀏覽器哪些外部 Origin 可以讀取資源。真正執行允許或阻擋的是瀏覽器,不是標頭本身在網路上攔截請求。
這個差異非常重要。部分跨來源請求仍可能到達伺服器並造成副作用,只是瀏覽器不把回應內容交給呼叫端 JavaScript;curl、Server-to-Server Client、惡意程式與代理也能自行送出 Origin,完全不受瀏覽器 CORS 檢查約束。因此 API 仍需獨立完成 Authentication、Authorization、輸入驗證、Rate Limit 與 CSRF 防護。
安全設計應從最小分享面開始:不需要跨來源存取的 Route 完全不回傳 CORS 標頭;公開且不含 Credential 的資源才考慮星號;需要 Session、Cookie 或 Authorization Context 的敏感 API,只允許經業務核准的精確 Origin,並將 Policy 集中管理、測試與監控。
- 逐一確認哪些 Route 真正需要被跨來源 JavaScript 讀取,不在整個網域全域開啟 CORS
- 把 Origin 當成瀏覽器分享條件,不把它當成使用者身分或伺服器端授權依據
- 公開匿名資源與敏感 Credential API 使用不同 Policy,不共用寬鬆預設值
- 實際端點永遠重新驗證身分、物件權限、租戶、方法與輸入,不信任 Preflight 已經授權業務操作
- 從應用程式、API Gateway、CDN、Reverse Proxy 到錯誤頁都要套用一致策略
Origin 與 Same-Origin Policy 到底怎麼判斷?
Origin 由 Scheme、Host 與 Port 三個部分組成。https://app.example.com 與 http://app.example.com 的 Scheme 不同;https://app.example.com 與 https://api.example.com 的 Host 不同;https://app.example.com 與 https://app.example.com:8443 的 Port 不同,因此都不是相同 Origin。Path、Query 與 Fragment 不參與 Origin 比較。
瀏覽器的 Same-Origin Policy 主要限制跨來源讀取,但跨來源寫入與嵌入在不少情況下仍然允許,例如表單提交、導覽、圖片或部分資源載入。這也是為什麼『CORS 阻擋錯誤』不代表請求沒有送達,更不代表狀態變更天然不受 CSRF 影響。
Origin 是結構化安全邊界,Allowlist 也應保存標準化後的完整 Origin,而不是只保存網域片段。比較時要使用成熟 URL Parser,明確處理預設 Port、大小寫、IDN 與尾點,不接受 Path、Userinfo 或萬用字元被混入 Origin 規則。
| URL | 是否同 Origin | 原因 |
|---|---|---|
| https://app.example.com/dashboard | 是 | 只有 Path 不同 |
| https://app.example.com:443/api | 通常等價 | HTTPS 明確預設 Port 與省略 Port 正規化後相同 |
| http://app.example.com | 否 | Scheme 不同 |
| https://api.example.com | 否 | Host 不同 |
| https://app.example.com:8443 | 否 | Port 不同 |
| https://app.example.com.attacker.example | 否 | 真正 Host 是攻擊者網域 |
一次 CORS 請求與回應是怎麼運作的?
前端程式在 app.example 發出 fetch,目標是 api.example。瀏覽器在跨來源 Request 加入 Origin,伺服器根據 Route 與 Origin Policy 決定是否回傳 Access-Control-Allow-Origin。收到回應後,瀏覽器比較標頭與原始請求;符合規則才把 Status、Body 與允許曝光的 Header 交給 JavaScript。
若回應缺少正確 ACAO,瀏覽器通常把它呈現為 CORS Error,JavaScript 只知道網路呼叫失敗,未必能讀到詳細原因。但伺服器可能已經處理 Request、寫入資料、寄出信件或記錄日誌;測試者不能只看前端錯誤就判定後端沒有副作用。
下圖將『請求能到達 API』與『回應能被前端讀取』分成兩條邊界。核准 Origin 的回應通過瀏覽器分享閘門;未核准 Origin 即使能送出某些 Request,也不能靠 CORS 取得可讀資料。

Simple Request 與 Preflight Request 有什麼不同?
某些跨來源 GET、HEAD 或 POST 在方法、Header 與 Content-Type 都符合 CORS Safelist 時,不會先送 OPTIONS,而是直接送出實際 Request。這類請求常被稱為 Simple Request。伺服器仍需回傳正確 CORS 標頭,瀏覽器才會把回應交給 JavaScript,但後端副作用可能已經發生。
非 Safelisted Method、Header 或 Content-Type 通常會觸發 Preflight。瀏覽器先送不帶 Credential 的 OPTIONS,包含 Origin、Access-Control-Request-Method 與可能的 Access-Control-Request-Headers;伺服器回覆核准的 Origin、Method、Header 與其他條件,瀏覽器才決定是否送出後續實際 Request。
Preflight 是瀏覽器的協定協商,不是應用程式業務授權。攻擊者可用非瀏覽器 Client 直接跳過 OPTIONS,合法瀏覽器的實際 Request 也可能帶著已變動的 Session、資源或輸入。因此 OPTIONS 與實際 Route 都要有一致的 CORS Policy,而身分與物件授權必須在實際 Route 重新執行。
| 面向 | Simple Request | Preflighted Request |
|---|---|---|
| 前置 OPTIONS | 沒有 | 瀏覽器通常先送 OPTIONS |
| 常見條件 | GET/HEAD/部分 POST 與 Safelisted Header/Content-Type | PUT/PATCH/DELETE、自訂 Header、JSON 等非 Safelisted 組合 |
| 實際 Request 是否可能到達 | 會直接送出 | Preflight 通過後才由瀏覽器送出 |
| CORS 失敗的效果 | 阻擋 JavaScript 讀取回應,不保證阻擋副作用 | 瀏覽器通常不送實際 Request,但非瀏覽器 Client 不受限制 |
| 仍需的控制 | CSRF、Authentication、Authorization、輸入驗證 | 實際 Route 同樣需要全部應用層控制 |
CORS 相關 Header 各自負責什麼?
CORS Policy 不是只設定 ACAO。Request 端的 Origin 表示發起頁面;Preflight 另外帶上預計使用的 Method 與 Header。Response 端則分別控制可讀 Origin、Credential、Method、Request Header、可被 JavaScript 讀取的 Response Header,以及 Preflight Cache 時間。
敏感 API 應只回傳必要項目。允許 POST 不代表也要允許 DELETE;允許 Content-Type 不代表要允許任意 Authorization 或內部除錯 Header;前端只需要讀取 Request ID 時,不應把所有 Response Header 暴露。
| Header | 方向 | 安全用途與注意事項 |
|---|---|---|
| Origin | Request | 表示發起頁面的序列化 Origin;可被非瀏覽器 Client 偽造,不能作為身分驗證 |
| Access-Control-Allow-Origin | Response | 只接受單一精確 Origin 或公開匿名資源使用星號,不是多值清單 |
| Access-Control-Allow-Credentials | Response | 只有大小寫完全正確的 true 有效;表示帶 Credential 的回應可被分享 |
| Access-Control-Request-Method | Preflight Request | 瀏覽器說明後續實際 Request 預計使用的方法 |
| Access-Control-Allow-Methods | Preflight Response | 只列該 Route 業務需要的方法 |
| Access-Control-Request-Headers | Preflight Request | 瀏覽器列出後續實際 Request 預計使用的非 Safelisted Header |
| Access-Control-Allow-Headers | Preflight Response | 只允許必要 Header,不把任意內部 Header 交給外部 Origin |
| Access-Control-Expose-Headers | Response | 額外允許 JavaScript 讀取指定 Response Header |
| Access-Control-Max-Age | Preflight Response | 控制 Preflight 結果可快取時間,Policy 變更與撤銷需納入考量 |
| Vary: Origin | Response/Cache | 動態回傳不同 ACAO 時,避免快取把一個 Origin 的回應錯配給另一個 |
哪些 CORS 設定真的會形成資料外洩風險?
單看到 Access-Control-Allow-Origin 並不等於弱點。要形成有意義的 CORS 資料外洩,通常需要目標回傳敏感資料、攻擊者能從未授權 Origin 發起瀏覽器請求、Policy 允許該 Origin 讀取,並且受害者的 Credential 或回應本身足以取得資料。
最常見根因包括:反射任意 Origin、Allowlist 字串比對錯誤、過度寬鬆的子網域 Regex、信任 null Origin、錯誤開放測試/預覽網域、已被接管的允許子網域,以及 Gateway 與應用程式同時加標頭造成不一致。
風險評估要確認實際可讀內容、需要的使用者狀態、能否跨租戶、是否只限公開資料,以及攻擊情境可否在主流瀏覽器穩定重現。『Header 看起來寬鬆』是線索,不是完整證據。
| 現象 | 是否通常可利用 | 需要進一步確認 |
|---|---|---|
| 公開靜態資源使用 ACAO 星號且不帶 Credential | 多半是預期設計 | 資源是否真的公開且無個資、Token 或內部 Header |
| 敏感 API 反射任意 Origin 並允許 Credential | 高風險 | 登入 Cookie/Client Certificate 是否隨請求送出,回應是否可讀 |
| 只回傳 ACAC true,沒有匹配 ACAO | 通常不足以分享回應 | 其他層是否補上或重複 ACAO |
| ACAO 星號搭配 ACAC true | Credential 模式下瀏覽器會拒絕分享 | 匿名 Response 是否仍含敏感資料,其他端點是否動態反射 Origin |
| 允許公司所有子網域 | 取決於子網域信任邊界 | 是否有使用者內容、第三方 SaaS、懸空 DNS 或可被接管 Host |
| 未授權 Origin 收到 200 但沒有 ACAO | JavaScript 通常無法讀取 | 實際 Request 是否造成 CSRF 或其他副作用 |
為什麼不能把 Request Origin 原樣反射回去?
有些 Middleware 為了支援多個前端,讀取 Origin 後直接輸出相同的 Access-Control-Allow-Origin。若沒有先做精確 Allowlist,任何攻擊者網站都能讓 API 回覆自己的 Origin;一旦同時允許 Credential,攻擊者頁面便可能在受害者登入狀態下讀取敏感回應。
正確的動態設定是:Parser 取得序列化 Origin,與伺服器端固定 Allowlist 做完全比對;只有命中才回傳該單一 Origin,未命中就完全省略 CORS Allow Header。不能回傳逗號分隔的多個 Origin,也不應在錯誤情況下退回星號。
Allowlist 來源也要受治理。租戶自助輸入 Custom Origin 時,應驗證完整 HTTPS Origin、證明網域控制權、限制租戶只能影響自己的資料,並保留新增、修改、撤銷與最後使用紀錄。
- 比較完整 Scheme、Host、Port,不比較原始字串片段
- 命中 Allowlist 才輸出相同 ACAO;未命中省略標頭並記錄 Policy 結果
- 不要回傳多個 ACAO Header,也不要把多個 Origin 串在一個 ACAO 值
- Custom Origin 要有網域所有權驗證、租戶隔離、審批與生命週期管理
- Policy 不可由前端 Query、Host Header 或未驗證租戶欄位直接決定
字串、Regex 與子網域 Allowlist 最容易錯在哪裡?
contains、startsWith 與模糊 endsWith 都不適合判斷 Origin。允許 example.com 的規則可能誤收 example.com.attacker.example;只檢查字串中出現 trusted.example,則可被 Userinfo、Query 或攻擊者 Host 混淆。應先解析為 Origin Tuple,再對正規化結果做精確比對。
若確實要允許受控子網域,規則必須鎖定 Scheme、Port 與 Label 邊界,而且組織要能保證每一個匹配子網域都同樣可信。Preview、User Content、行銷活動、第三方 Hosting 與已廢棄 DNS 往往不應與正式 API 共享 Credential CORS。
以 Regex 實作時要有正負測試向量,並避免災難性回溯造成 DoS。更穩健的做法是保存明確 Origin 集合,或由受治理的服務註冊表生成 Policy,而不是維護難以審查的萬用 Regex。
| 做法 | 問題 | 建議 |
|---|---|---|
| origin.includes('example.com') | 攻擊者 Host 只要包含該字串就可能通過 | 禁止使用 |
| origin.endsWith('example.com') | 缺少 Label 邊界時會接受 notexample.com | 解析 Host 後精確比對或驗證點號邊界 |
| 允許 *.example.com | 所有子網域都成為信任來源,包含可接管或使用者內容 | 列出真正需要的完整 Origin |
| 原始字串與固定 Origin 完全相等 | 若未統一預設 Port、大小寫與尾點可能出現不一致 | 使用 URL Parser 取得序列化 Origin 再比較 |
| 受治理的精確 Origin Set | 需維護新增與撤銷流程 | 敏感 Credential API 的優先選擇 |
Credential、Cookie、Authorization 與 SameSite 要怎麼一起看?
Credential Mode 為 include 時,瀏覽器可能附帶 Cookie、HTTP Authentication 或 TLS Client Certificate,但是否真的帶 Cookie 還受 Domain、Path、Secure、SameSite 與瀏覽器第三方 Cookie Policy 影響。Client 設定 include 不等於一定有 Credential;伺服器回傳 ACAC true 也不會自行建立使用者身分。
帶 Credential 的 CORS 回應要同時具有精確匹配的 ACAO 與大小寫完全正確的 Access-Control-Allow-Credentials: true。ACAO 星號不能在 Credential Mode 下分享回應;Allow-Methods、Allow-Headers 與 Expose-Headers 的星號語意在 Credential 情境也受到限制,應列出明確值。
SameSite 與 CORS 解決不同問題。SameSite 控制 Cookie 在跨 Site 情境是否送出;CORS 控制跨 Origin JavaScript 是否可讀回應。Site 與 Origin 也不是同一概念,因此應分別設計 Cookie、CSRF 與 CORS Policy,不能用其中一項宣稱取代其他控制。
為什麼 Access-Control-Allow-Origin: null 不安全?
部分沒有一般階層式 Origin 的文件會被序列化為 null,例如某些 data 或 file 情境,以及 Sandboxed Document。攻擊者可能建立具有 opaque/null Origin 的惡意文件,因此把 ACAO 設為 null 並不等於『不允許任何網站』。
MDN 明確建議避免使用 Access-Control-Allow-Origin: null。若業務有本機檔案、Sandboxed Iframe、行動 WebView 或特殊內嵌環境需求,應重新設計信任通道,例如使用原生 Bridge、短效單次 Token、明確 App Attestation 或受控 Custom Scheme,而不是把所有 null Origin 視為同一可信主體。
檢測時要確認伺服器是否反射 null、是否同時允許 Credential,以及敏感 Route 是否能被 null Origin 文件讀取。測試必須在隔離頁面與授權環境進行,不使用客戶資料或真實高權限 Session。
Preflight 應該怎麼安全處理?
OPTIONS Handler 應先依 Route 取得 CORS Policy,再核對 Origin、預計 Method 與 Request Header。只回覆該 Route 真正支援的 Method 與必要 Header,未核准 Origin 不輸出 ACAO;不要因為 OPTIONS 本身不改資料,就全域允許任意來源、方法與 Header。
Fetch Standard 規定 CORS Preflight 的 Credential Mode 為 same-origin,亦即 Preflight 本身不帶 Credential,但回應仍需表達後續實際 Request 是否允許 Credential。Gateway 若強制所有 OPTIONS 都先通過使用者 Session 驗證,可能造成合法 Preflight 失敗;反過來,OPTIONS 成功也不能跳過實際 Request 的 Authentication 與 Authorization。
錯誤回應、404、401、403 與 5xx 也要有明確策略。合法前端可能需要讀取結構化錯誤,但不能讓全域錯誤處理器反射任意 Origin;同一 Route 的成功與錯誤 CORS 規則應一致,避免只有 Exception Path 暴露資料。
| 步驟 | 檢查 | 拒絕條件 |
|---|---|---|
| 1. Route Policy | 此 Route 是否支援跨來源存取 | 不需要 CORS 就省略所有 Allow Header |
| 2. Origin | 序列化 Origin 是否精確命中 Allowlist | 未知、null、HTTP 或錯誤 Port 不符合 Policy |
| 3. Method | Access-Control-Request-Method 是否為 Route 必要方法 | 未支援或高權限方法未核准 |
| 4. Headers | 每個 Request Header 是否在最小清單 | 內部、代理、除錯或未預期 Header |
| 5. Credentials | 此 Origin/Route 是否允許後續 Credential | 公開匿名 Policy 不應開啟 Credential |
| 6. Response | 200/204 與精確 Allow Header、適當 Max-Age | 不回傳萬用寬鬆預設或多個 ACAO |
Vary: Origin、CDN 與 Preflight Cache 為什麼會影響安全?
伺服器若依 Request Origin 動態回傳不同 ACAO,回應也應包含 Vary: Origin,讓 Browser Cache、CDN 與其他中介知道快取內容會隨 Origin 改變。沒有正確 Vary,可能造成合法前端收到缺少標頭的快取版本,也可能讓不該共用的回應與 CORS 標頭被錯配。
Access-Control-Max-Age 只控制 Preflight 結果快取,不是 API 資料快取。時間過長會延後 Policy 撤銷生效;時間太短則增加 OPTIONS 負載。值應依 Origin 變更頻率、事件撤銷需求與瀏覽器上限設計,並讓緊急事件能透過 CDN Purge、版本化 Route 或暫停 Credential CORS 快速收斂。
若 ACAO 永遠是同一固定 Origin,或公開匿名資源永遠使用星號,Fetch Standard 建議一致地在所有相關回應送出該值;若是動態 Allowlist,就使用 Vary: Origin。還要確認 CDN Cache Key、Error Cache 與 Gateway Response Transform 沒有刪除或合併標頭。
API Gateway、Reverse Proxy 與微服務最常發生哪些 Policy Drift?
CORS 可能同時在 CDN、WAF、API Gateway、Ingress、Framework Middleware 與應用程式設定。多層都加標頭時,可能出現重複 ACAO、成功與錯誤回應不同、OPTIONS 在 Gateway 放行但實際 Route 由服務拒絕,或服務精確 Allowlist 被外層星號覆蓋。
Policy 應有單一權威來源與清楚責任邊界。若由 Gateway 管理,後端服務不要再自行生成另一套;若各服務有不同業務 Origin,Gateway 應從受治理的 Route Metadata 產生設定,而不是套用組織全域的寬鬆 Regex。
部署驗證要從外部實際入口測試,而不只單元測試 Middleware。需覆蓋 CDN Hit/Miss、壓縮、HTTP/2、錯誤頁、重新導向、版本路由、Legacy Domain、區域站點與藍綠部署,確認每一條對外路徑輸出的 CORS 結果一致。
| 位置 | 常見 Drift | 控制方式 |
|---|---|---|
| CDN | 快取未區分 Origin、Error Cache 缺少或錯配標頭 | 明確 Cache Key/Vary、Purge 流程與端到端測試 |
| API Gateway | 全域星號、OPTIONS 與實際 Route Policy 不一致 | Route 級 Policy、受治理設定與部署驗證 |
| Ingress/Proxy | 重複 ACAO、合併 Header、只在 2xx 加標頭 | 指定唯一生成層並測試 4xx/5xx |
| Framework Middleware | 預設反射 Origin、允許所有 Method/Header | 精確 Allowlist、最小設定與版本鎖定 |
| Microservice | 內部 Route 誤以為不會對外,卻被 Gateway 暴露 | 資產與 Route Inventory、預設無 CORS、外部暴露審查 |
CORS、CSRF、CSP、CORP 與 COEP 有什麼不同?
CORS 允許特定跨 Origin JavaScript 讀取回應;CSRF 防護阻止攻擊者網站借用受害者登入狀態執行非本人意圖的操作;CSP 主要限制頁面可載入與執行哪些資源,降低 XSS 與內容注入影響。三者解決不同安全問題。
Cross-Origin-Resource-Policy(CORP)由資源宣告哪些 Site/Origin 能載入它;Cross-Origin-Embedder-Policy(COEP)要求文件只嵌入明確允許的跨來源資源;Cross-Origin-Opener-Policy(COOP)則隔離 Browsing Context Group。這些標頭常與 Cross-Origin Isolation 一起使用,但不能拿來取代 API 的 CORS Allowlist。
設計時先寫清楚需求:誰能送出操作、誰能讀取資料、頁面能載入什麼、文件要與哪些跨來源內容隔離。再把 Authentication/Authorization、CSRF、CORS、CSP/CORP/COEP 分別套在對應邊界,避免一個 Header 承擔不存在的保證。
| 控制 | 主要保護問題 | 不能取代 |
|---|---|---|
| CORS | 跨 Origin JavaScript 是否能讀取回應 | API Authentication/Authorization、CSRF |
| CSRF 防護 | 瀏覽器是否能借用使用者狀態執行非本人意圖操作 | CORS 資料分享與物件授權 |
| CSP | 頁面可載入/執行的資源與 Script 行為 | 輸出編碼、Sanitization、CORS |
| CORP/COEP | 跨來源資源載入與文件嵌入隔離 | API Route 的 CORS Allowlist |
| API Authorization | 目前主體能否對目前物件執行動作 | 瀏覽器 CORS 與 CSRF |
如何在授權範圍內安全檢測 CORS?
先盤點對外 API、前端 Origin、Credential 類型、CDN/Gateway 與 Route Owner,取得允許的測試帳號、租戶與資料。測試 Origin 必須由測試團隊控制,不使用真實攻擊網域;只讀取專用測試資料,不觸碰其他使用者、客戶或未授權租戶。
建立正常基線:合法 Origin、無 Origin、未授權 Origin、null、HTTP/HTTPS 差異、相似 Host、子網域與錯誤 Port。對每個案例同時記錄 OPTIONS 與實際 Request/Response,包含 ACAO、ACAC、Allow-Methods、Allow-Headers、Expose-Headers、Max-Age、Vary、Status 與 Cache Header。
Header 工具只能確認伺服器輸出,最後仍要使用真正瀏覽器頁面執行 fetch,分別測試 credentials omit 與 include,證明攻擊者 Origin 的 JavaScript 能否讀取 Body/Header。若發現可讀敏感資料,立即停止擴大,遮罩證據並依事件流程處理。
- 測試公開、登入、管理、匯出、錯誤與 Legacy Route,不只首頁或單一路徑
- Origin 測試採單一變因:Scheme、Host、Port、子網域、相似字串與 null 分開驗證
- Preflight 與實際 Request 成對保存,確認 Method/Header 只開最小集合
- 分別驗證 Cookie、Authorization Header 與無 Credential 情境,不假設 include 一定帶 Cookie
- 從正式對外 CDN/Gateway 入口測試 Cache Hit/Miss、4xx/5xx 與重新導向
- 使用專用測試資料與最小權限帳號,禁止讀取真實客戶或跨租戶資料
一筆可信的 CORS 發現需要哪些證據?
報告要區分 Configuration Observation 與 Exploitable Finding。Observation 可記錄某 Route 反射 Origin 或開放星號;要證明可利用,則需提供攻擊者控制 Origin 的瀏覽器程式成功讀取了什麼資料、使用何種 Credential、對哪個角色/租戶有效,以及伺服器與瀏覽器實際交換的 Header。
證據至少包含完整 Origin Tuple、Route、Method、Preflight、實際 Response、Browser Console/Network、可讀 Body 的最小遮罩片段、測試帳號與時間。不要只貼 curl 回應,因為 curl 不執行瀏覽器 CORS Policy;也不能只貼 Console CORS Error,因為 Error 反而可能表示資料無法被 JavaScript 讀取。
影響描述應避免誇大。能讀公開資料、能讀自己帳號資料、能讀跨租戶資料、能執行狀態變更是不同風險;CORS 資料讀取與 CSRF 副作用也要分開證明。
| 證據 | 應記錄內容 | 判讀要求 |
|---|---|---|
| 攻擊 Origin | 完整 Scheme/Host/Port 與控制權 | 不是只提供網域字串或修改 Host Header |
| Preflight | Origin、Request Method/Headers 與全部 Allow Response | 確認瀏覽器是否會送出實際 Request |
| 實際 Request | Credential Mode、Cookie/Authorization 是否存在、Route 與 Method | 遮罩 Session、Token 與個資 |
| 瀏覽器讀取 | 測試頁程式、Network/Console 與可讀 Response | 證明 JavaScript 取得資料,不只伺服器回 200 |
| 資料敏感度 | 角色、租戶、資料類型與最小遮罩片段 | 不擴大讀取未授權資料 |
| 部署路徑 | CDN、Gateway、Cache Hit/Miss、應用版本 | 定位是哪一層生成或覆蓋 Policy |
安全 CORS Policy 應如何實作與治理?
建立 Route 級 Policy Model,至少包含是否公開、允許的精確 Origin、Credential、Method、Request Header、Expose Header、Max-Age 與 Owner。預設狀態是不輸出 CORS Header;只有經審查的 Route 才啟用,並由中央 Middleware 或 Gateway 統一產生。
Policy 判斷先解析序列化 Origin,再與固定 Set 完全比對。命中後回傳單一 ACAO,Credential Route 明確回傳 ACAC true,動態 Origin 加上 Vary: Origin;未命中不回傳 ACAO。實際 Route 仍執行完整 Authentication、Authorization 與 CSRF 決策。
把 Policy 當成程式碼治理:Code Review、Owner、到期日、變更紀錄、測試向量、部署差異與緊急撤銷。新增 Temporary Preview 或 Partner Origin 時必須限定環境與期限,正式環境不得自動繼承開發站的寬鬆設定。
| Route 類型 | 建議 ACAO/Credential | 其他要求 |
|---|---|---|
| 不需跨來源的內部/同 Origin API | 省略全部 CORS Allow Header | 仍需 Authentication、Authorization、CSRF 與網路控制 |
| 真正公開的匿名資料/靜態資源 | ACAO 星號,省略 ACAC | 確認 Response 不含個資、租戶資料、秘密與敏感 Header |
| 受信任前端使用的敏感 API | 精確 Origin Allowlist;需要時 ACAC true | Vary: Origin、最小 Method/Header、完整身分與物件授權 |
| 第三方 Partner API | 優先使用 Server-to-Server Authentication,不依賴瀏覽器 CORS | OAuth/mTLS/API Key、Scope、Rate Limit 與審計 |
| 多租戶 Custom Origin | 驗證網域控制權後綁定租戶與 Route | 租戶隔離、審批、撤銷、到期與 Audit Log |
如何把 CORS 測試做進 CI/CD?
Policy Unit Test 應涵蓋每個允許 Origin 的正向案例,以及相似 Host、錯誤 Scheme/Port、null、空值、尾點、IDN、子網域與攻擊者後綴的負向案例。測試輸出不只看 Status,也要確認每個 CORS Header 是否精確存在或完全省略。
Integration Test 要啟動真實 Gateway/Middleware,成對測試 OPTIONS 與實際 Request,覆蓋成功、401、403、404、429、500、Redirect 與 Cache。End-to-End Test 則從兩個不同 Origin 的瀏覽器頁面執行 fetch,確認合法前端可用、未授權前端無法讀取。
部署後以合成監測持續檢查重要 Route。當 CDN、Gateway、Framework 或 Domain 清單變更時,自動重跑 CORS Matrix;IaC/Configuration Scan 則阻止全域星號、任意 Origin Reflection 與 Credential 寬鬆預設進入正式環境。
| 層次 | 應驗證項目 | 通過條件 |
|---|---|---|
| Policy Unit | Origin 解析、精確 Allowlist、Method/Header/Credential | 所有正負向向量得到唯一且可預期決策 |
| Middleware Integration | OPTIONS、實際 Request、錯誤與 Redirect | 每種 Response 的 Allow Header 與 Route Policy 一致 |
| Gateway/CDN | 外部入口、Cache Hit/Miss、Vary 與多環境 | 沒有重複、覆蓋或跨 Origin Cache 錯配 |
| Browser E2E | 合法與未授權 Origin 的 omit/include fetch | 合法功能成功;未授權 JavaScript 無法讀取 |
| Deployment Guardrail | 設定檔、IaC、Domain Inventory 與 Owner | 禁止未核准星號、Reflection、null 與過期 Origin |
日誌、監控與告警應該看哪些 CORS 訊號?
記錄 Route、序列化 Origin、Policy ID/Version、Allow/Deny、Credential 類型、Method、Requested Headers、Response Status、Gateway/Service、Cache 狀態與 Request ID。不要記錄完整 Cookie、Authorization、Response Body 或個資。
高價值告警包括:敏感 Route 突然出現新 Origin、未核准 Origin 大量嘗試 Credential Request、null Origin、相似公司網域、Policy 從精確清單改為星號、同一 Response 出現多個 ACAO,以及 CDN/Gateway 與應用程式決策不一致。
監控不能只看被拒絕請求。應定期比對實際允許 Origin 與資產清單,找出已下線、轉由第三方管理、DNS 懸空或失去 Owner 的網域;這些 Origin 即使長期沒有流量,也可能在遭接管後成為讀取敏感 API 的跳板。
發現可利用 CORS 設定錯誤後怎麼處理?
先縮小分享面:在受影響 Route 移除任意 Reflection、null 與不必要 Origin,必要時暫停 Credential CORS;由 CDN/Gateway 清除相關快取並降低 Preflight Max-Age。若無法立即部署程式,可先在唯一權威邊界套用精確 Allowlist,但要避免另一層重新加回寬鬆標頭。
若攻擊者可能已讀取敏感 Response,保全 Gateway、Application、Browser Telemetry、DNS 與 Audit Log,查找異常 Origin、使用者 Session 與資料存取。依資料敏感度撤銷可能暴露的 Session/Token,檢查被允許子網域是否遭接管,並評估通知義務。
根因修復後盤點同一 Middleware、Gateway Policy、Domain Regex 與所有環境。問題通常不是單一路由,而是共用 CORS 設定或部署層 Drift;只封鎖已知攻擊網域無法解決任意 Origin Reflection。
- 立即移除任意 Origin Reflection、null 與非必要 Credential 分享
- 清除 CDN/Browser 可控範圍的快取,必要時版本化 Route 或暫停跨來源功能
- 保全 Origin、Policy Version、Route、Session 與資料存取日誌並遮罩敏感資訊
- 撤銷可能暴露的 Session/Token,檢查允許網域控制權與 Subdomain Takeover
- 修復共用 Middleware/Gateway 根因,讓所有成功、錯誤與 Legacy Route 共用策略
- 完成真正瀏覽器、Gateway/CDN、跨帳號與合法前端的全面複驗
修復後如何複驗,才能確認沒有旁路?
先以原始攻擊 Origin 重測,確認 Preflight 或實際 Response 不再回傳匹配 ACAO,且瀏覽器 JavaScript 無法讀取 Body/Header。再測相似 Host、錯誤 Scheme/Port、子網域、null、無 Origin 與不同 Credential Mode,避免只封鎖單一字串。
接著覆蓋成功與所有錯誤路徑、CDN Hit/Miss、Gateway、直接服務入口、舊版 API、不同區域與藍綠版本。確認沒有重複 ACAO、動態回應包含 Vary: Origin、Preflight Method/Header 最小化,且實際 Route 的 Authentication/Authorization/CSRF 仍完整執行。
最後驗證合法前端不受破壞:登入、Token 更新、錯誤處理、上傳、下載與必要 Response Header 都能使用。複驗報告要列出 Policy Version、部署層、測試 Origin Matrix、Browser 版本、Cache 狀態與未涵蓋限制。
| 複驗面向 | 應通過結果 | 常見殘留 |
|---|---|---|
| 原始案例 | 未授權 JavaScript 無法讀取敏感回應 | 只在應用層修正,Gateway 仍反射 Origin |
| Origin 變體 | Scheme/Host/Port、null、子網域與相似字串全部依 Policy 判斷 | 只封鎖 PoC 網域或使用脆弱 Regex |
| Credential | 只有核准 Origin 能取得 Credential Response | 公開與敏感 Route 共用 ACAC/星號設定 |
| Preflight/實際 Route | Method/Header 最小化,實際操作仍完成全部授權 | OPTIONS 正確但實際錯誤頁洩漏,或把 Preflight 當授權 |
| Cache/部署層 | Vary、CDN、Gateway、各版本與錯誤回應一致 | Cache Hit/Miss 或區域部署結果不同 |
| 合法功能 | 所有核准前端與必要 Header 正常 | 過度封鎖導致團隊另開未治理旁路 |
給前端、後端、平台與資安團隊的 CORS 檢查表
CORS 的安全性取決於整條瀏覽器到 API 的路徑,而不是某個 Framework 選項。前端要正確選擇 Credential Mode;後端要完成精確 Route Policy 與業務授權;平台要確保 Gateway、CDN、Cache 與錯誤回應一致;資安則要用真正瀏覽器與測試資料證明回應能否被讀取。
- 不需要跨來源的 Route 完全不回傳 CORS Allow Header
- 公開匿名資源才使用 ACAO 星號,並確認 Response 不含 Credential 或敏感資料
- 敏感 API 使用精確 Scheme/Host/Port Allowlist,不反射任意 Origin、不信任 null
- Credential、Method、Request Header 與 Expose Header 全部採最小必要設定
- 動態 ACAO 回應加入 Vary: Origin,並驗證 CDN/Gateway Cache Key 與錯誤路徑
- 實際 Route 永遠執行 Authentication、Authorization、CSRF 與輸入驗證
- 所有允許 Origin 都有 Owner、用途、環境、到期日、網域控制權與撤銷流程
- CI 與上線後監測包含 OPTIONS、實際 Request、Browser E2E、Cache 與多部署層
資料來源
- WHATWG Fetch Standard — CORS protocol↗
- OWASP Web Security Testing Guide — Testing Cross Origin Resource Sharing↗
- OWASP HTML5 Security Cheat Sheet — Cross Origin Resource Sharing↗
- OWASP REST Security Cheat Sheet — CORS↗
- MDN — Cross-Origin Resource Sharing↗
- MDN — Same-origin policy↗
- MDN — Access-Control-Allow-Origin↗
- MDN — CORS configuration↗


