Post

Meow's Web - CORS & Credential Misconfiguration (CWE-942)

CORS - Cross-Origin Resource Sharing 跨源资源共享


概述 Overview

CORS(跨源资源共享)

  • Cross-Origin Resource Sharing
  • 是浏览器实现的一套安全机制,用于在保留同源策略(Same-Origin Policy)保护的前提下,有选择地允许网页向不同源的服务器发起请求。
  • a browser-enforced mechanism that selectively relaxes the Same-Origin Policy, allowing web pages to request resources from a different origin.
  • The server declares which cross-origin access is permitted via response headers.

理解 CORS 的关键点 Key insight:

  • not a server-side security
  • it is a browser-enforced policy.
  • protects the browser’s user from unauthorized cross-origin requests made on their behalf.
  • 防止恶意网站代表用户发起未授权的跨源请求。

基础:同源策略 Foundation: Same-Origin Policy

什么是”源” What Is an “Origin”

“源”由三个部分组成,三者必须完全匹配才算”同源”。

An origin is the combination of three parts. All three must match to be considered the same origin.

组成部分 Component示例 Example
协议 Protocolhttps
域名 Hostapp.example.com
端口 Port443
1
2
3
https://app.example.com:443  vs  http://app.example.com      → 跨源 (protocol differs)
https://app.example.com:443  vs  https://api.example.com     → 跨源 (host differs)
https://app.example.com:443  vs  https://app.example.com:8080 → 跨源 (port differs)

同源策略的作用 What SOP Protects

浏览器默认阻止 JavaScript 读取来自不同源的响应。这样即使浏览器代表用户发出了请求,恶意页面的 JavaScript 也无法读取响应内容。

The browser blocks JavaScript from reading responses from a different origin by default. Even if the browser sends the request on the user’s behalf, a malicious page’s JavaScript cannot read the response body.


基础概念 Basic Concepts

如果浏览器支持 HTML5,就可以使用新的跨域策略:CORS,是 HTML5 规范定义的如何跨域访问资源。

CORS is the HTML5-defined standard for cross-origin resource access. The browser enforces it on every cross-origin request.

流程 Flow:

  1. Origin 本域,也就是浏览器当前页面的域 / the browser’s current page domain
  2. JavaScript 向外域(如 sina.com)发起请求 / JavaScript makes a request to an external domain
  3. 浏览器收到响应后,首先检查 Access-Control-Allow-Origin 是否包含本域 / browser checks if Access-Control-Allow-Origin includes the current origin
    1. 如果是,跨域请求成功 / if yes, the cross-origin request succeeds
    2. 如果不是,请求失败,JavaScript 将无法获取到响应的任何数据 / if no, the request fails and JavaScript receives no data

CORS

假设本域是 my.com,外域是 sina.com,只要响应头 Access-Control-Allow-Originhttps://my.com*,本次请求就可以成功。跨域能否成功,取决于目标服务器是否设置了正确的 Access-Control-Allow-Origin,决定权始终在目标服务器手中。

If the current page is my.com and it requests sina.com, the request succeeds only if sina.com’s response includes Access-Control-Allow-Origin: https://my.com or Access-Control-Allow-Origin: *. The server controls whether access is granted.


CORS 请求类型 Request Types

简单请求 Simple Requests

以下条件均满足时,浏览器直接发送请求,无需预检。

When all conditions below are met, the browser sends the request directly without a preflight.

  • 方法为 GETHEADPOST
  • POSTContent-Type 仅限 application/x-www-form-urlencodedmultipart/form-datatext/plain
  • 不包含自定义请求头(如 X-Custom: 12345)/ no custom request headers

简单请求能满足约 90% 的普通跨域需求。

Simple requests cover approximately 90% of common cross-origin use cases.

预检请求 Preflight Requests (OPTIONS)

对于 PUTDELETE,以及 Content-Type: application/jsonPOST 请求,浏览器在发送实际请求前,会先自动发送一个 OPTIONS 预检请求,询问服务器是否允许该操作。

For PUT, DELETE, and POST with Content-Type: application/json, the browser automatically sends an OPTIONS preflight first to ask whether the server permits the operation.

预检请求 Preflight request:

1
2
3
4
5
OPTIONS /path/to/resource HTTP/1.1
Host: bar.com
Origin: https://my.com
Access-Control-Request-Method: POST
Access-Control-Request-Headers: Content-Type

服务器必须明确响应 Server must respond explicitly:

1
2
3
4
5
HTTP/1.1 200 OK
Access-Control-Allow-Origin: https://my.com
Access-Control-Allow-Methods: POST, GET, PUT, OPTIONS
Access-Control-Allow-Headers: Content-Type
Access-Control-Max-Age: 86400

浏览器确认响应中包含将要发送的请求方法后,才会继续发送实际请求;否则,抛出一个错误。

The browser verifies the response includes the requested method before proceeding. If not, it throws a CORS error and blocks the actual request.

由于以 POST、PUT 方式传送 JSON 格式的数据在 REST 接口中很常见,要跨域正确处理这类请求,服务器端必须正确响应 OPTIONS 预检请求。

Since POST and PUT with JSON is common in REST APIs, cross-origin handling requires the server to correctly respond to OPTIONS preflight requests.


CORS 安全隐患 Security Implications

关键响应头 Key Headers

响应头 Response Header作用 Purpose
Access-Control-Allow-Origin允许哪些源访问 / which origins may access the resource
Access-Control-Allow-Credentials是否允许携带 Cookie / Authorization 头 / whether cookies and auth headers are forwarded
Access-Control-Allow-Methods允许的 HTTP 方法 / permitted HTTP methods
Access-Control-Allow-Headers允许的请求头 / permitted request headers

为什么 * 单独使用是可接受的

Access-Control-Allow-Origin: *

  • 表示允许任何源读取响应
  • means any origin may read the response.
  • However, the browser will not attach cookies or Authorization headers in wildcard mode.
  • The request is anonymous
  • a foreign origin can read the response, but only unauthenticated public data. 即使外域 JavaScript 能读到响应,读到的也是未认证的公开数据。

适用场景 Safe for:

  • 公开 CDN 资源、开放 API 文档、无需登录态的只读接口。
  • public CDN assets, open API docs, read-only endpoints that require no authentication.

为什么 * + credentials=true 是危险组合 Why Wildcard + Credentials Is the Dangerous Combination

CORS_ALLOW_CREDENTIALS=true 告诉浏览器:对于跨源请求,允许携带 Cookie 和 Authorization 头。这意味着请求会带上用户已登录的会话凭证。

CORS_ALLOW_CREDENTIALS=true instructs the browser to attach cookies and Authorization headers on cross-origin requests — carrying the user’s authenticated session credentials.

CORS 规范的明确禁止 CORS spec explicitly prohibits this combination:

规范规定:当 allow_credentials=true 时,Access-Control-Allow-Origin 不能为 `*`,必须是具体的源。如果服务器返回了这个非法组合,标准浏览器会直接报错并拒绝请求。

The spec states: when allow_credentials=true, Access-Control-Allow-Origin must not be `*` — it must be a specific origin. A spec-compliant browser will error and block the request if it sees this illegal combination.

1
2
3
# 规范禁止的组合 Combination prohibited by spec:
Access-Control-Allow-Origin: *
Access-Control-Allow-Credentials: true

框架的静默反射行为 Framework Silent Origin Reflection

部分框架(包括 FastAPI、django-cors-headers 等)在配置 origins=['*']allow_credentials=True 时,不会在响应中写 *,而是将请求中的 Origin 头原样反射回去。

Some frameworks (FastAPI, django-cors-headers, and others), when configured with origins=['*'] and allow_credentials=True, do not echo * in the response — they silently reflect whatever Origin the request contained.

1
2
3
浏览器发送  Browser sends:        Origin: https://evil.com
服务器响应  Server responds:       Access-Control-Allow-Origin: https://evil.com
                                    Access-Control-Allow-Credentials: true

浏览器看到的是一个具体源(不是 *)加上 credentials=true,于是 允许请求携带 Cookie 发出,并允许 JavaScript 读取响应。这正是 CWE-942 描述的漏洞核心。

The browser sees a specific origin (not *) plus credentials=true, so it permits the request with cookies and allows JavaScript to read the response. This is the core of the CWE-942 vulnerability.


攻击场景 Attack Scenario

前提 Prerequisites:

  • 受害服务器配置了 CORS_ALLOW_ORIGINS=* + CORS_ALLOW_CREDENTIALS=true / victim server misconfigured with wildcard + credentials
  • 受害者已登录受害服务器(浏览器持有该服务器的 Session Cookie)/ victim is logged in and browser holds a session cookie
  • 受害者被诱导访问攻击者控制的页面(如通过钓鱼邮件)/ victim visits attacker’s page (e.g., via phishing)

攻击流程 Attack flow:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
1. 受害者登录 bank.com → 浏览器存储 session cookie
   Victim logs into bank.com → browser stores session cookie

2. 受害者访问 evil.com(攻击者页面)
   Victim visits evil.com (attacker's page)

3. evil.com 的 JavaScript 发起请求:
   evil.com JavaScript executes:

   fetch("https://bank.com/api/account", {
     method: "GET",
     credentials: "include"   // 携带 bank.com 的 cookie / attach bank.com's cookie
   });

4. 浏览器发送请求 Browser sends:
   GET /api/account HTTP/1.1
   Host: bank.com
   Origin: https://evil.com
   Cookie: session=abc123         ← 受害者的登录凭证 victim's session

5. bank.com(错误配置)响应 bank.com (misconfigured) responds:
   HTTP/1.1 200 OK
   Access-Control-Allow-Origin: https://evil.com   ← 反射请求 Origin reflected the request Origin
   Access-Control-Allow-Credentials: true

6. 浏览器判断:Origin 匹配 + credentials=true → 允许 JS 读取响应
   Browser: origin matches + credentials=true → allows JS to read the response body

7. evil.com 的 JS 读取到受害者的账户数据,数据泄露完成
   evil.com's JS reads the victim's account data — exfiltration complete

直觉理解:四实体思维模型 Intuitive Understanding: The Four-Entity Mental Model

常见误解有两种:一是攻击者必须将恶意链接注入银行页面;二是攻击者在中间人位置拦截重定向请求。实际上两者都不需要。恶意网站完全不接触银行页面——它直接从自己的域向银行 API 发起请求,利用浏览器充当”Cookie 快递员”。

Two common misconceptions: (1) the attacker must inject a malicious link into the bank page; (2) the attacker sits as a man-in-the-middle and intercepts a redirect. Neither is required. The malicious site never touches the bank’s page — it calls the bank API directly from its own domain, using the browser as a cookie courier.

四个实体及其角色 Four entities and their roles:

实体 Entity角色 Role
银行服务器 Bank server持有受害者账户数据;CORS 错误配置,将请求 Origin 反射并允许凭证 / Holds victim’s account data; CORS misconfigured to reflect the requesting origin with credentials enabled
用户浏览器 User browser存储 bank.com 的 Session Cookie;执行来自任意域的 JavaScript / Stores the bank.com session cookie; executes JavaScript from any domain
受害者 Victim已登录银行(Cookie 存在)且访问了恶意页面——两个动作相互独立 / Logged into the bank (cookie exists) AND visited the malicious page — two independent actions
恶意网站 Malicious website在自己的域内直接调用银行 API,让浏览器自动携带 Cookie / Calls the bank API directly from its own domain; the browser automatically carries the cookie

攻击发生时浏览器的判断过程 How the browser reasons during the attack:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
evil.com JS 执行 / evil.com JS runs:
  fetch("https://bank.com/api/account", { credentials: "include" })

浏览器检查 / Browser checks:
  1. evil.com 要我调用 bank.com — 这是跨源请求
     evil.com is asking me to call bank.com — this is cross-origin
  2. 请求附带 credentials: include — 需要携带 bank.com 的 Cookie
     Request has credentials: include — need to attach bank.com's cookie
  3. bank.com 回应 Access-Control-Allow-Origin: https://evil.com + credentials=true
     bank.com responded: Access-Control-Allow-Origin: https://evil.com + credentials=true
  4. 银行明确授权了 evil.com,Cookie 已附上,允许 JS 读取响应体
     Bank explicitly authorized evil.com — cookie attached, JS may read the response body

结果 / Result:
  evil.com JS 读取受害者账户数据,数据泄露完成
  evil.com JS reads the victim's account data — exfiltration complete

与 CSRF 的关键区别 Critical difference from CSRF:

CORS 错误配置比 CSRF 危害更大,核心在于响应可读性。

CORS misconfiguration is worse than CSRF because of response readability.

攻击类型 Attack需要注入银行页面 Needs injection需要中间人 Needs MITM可读取响应体 Can read response body
XSS是 Yes — injects into bank’s own page否 No是 Yes
CSRF否 No否 No否 No — can trigger actions but cannot read
CORS 错误配置 CORS misconfiguration否 No否 No是 Yes — server authorized the read

CSRF 只能代表用户触发操作(如转账),但读不到响应内容,攻击者无法确认结果也无法盗取数据。CORS 错误配置因为服务器明确授权了跨源读取,evil.com 的 JavaScript 可以直接读取响应体,账户数据直接暴露给攻击者。

CSRF can trigger actions on behalf of the user (e.g., wire transfers) but cannot read the response — the attacker cannot confirm the outcome or exfiltrate data. CORS misconfiguration is worse because the server explicitly authorizes the cross-origin read, so evil.com’s JavaScript reads the response body directly, exposing account data to the attacker.


影响 Impact:

  • 账户信息泄露 / account data exfiltration
  • 代表受害者执行操作(转账、修改密码等)/ performing state-changing actions on behalf of the victim
  • 完整的跨源会话劫持 / full cross-origin session hijacking

CWE / OWASP 映射 Mapping:

标识 ID描述 Description
CWE-942Permissive Cross-domain Policy with Untrusted Domains
OWASP A05:2021Security Misconfiguration
OWASP A01:2021Broken Access Control (credentials exposed across untrusted origins)

修复建议 Remediation

CORS_ALLOW_ORIGINS* 改为具体允许的来源列表,保留 allow_credentials=True

Replace CORS_ALLOW_ORIGINS=* with an explicit list of trusted origins. allow_credentials=True can remain if needed.

1
2
3
4
5
6
7
# 修复前 Before (insecure)
CORS_ALLOW_ORIGINS=*
CORS_ALLOW_CREDENTIALS=true

# 修复后 After (secure)
CORS_ALLOW_ORIGINS=https://app.example.com,https://admin.example.com
CORS_ALLOW_CREDENTIALS=true

Python / FastAPI 示例 FastAPI example:

1
2
3
4
5
6
7
8
9
from fastapi.middleware.cors import CORSMiddleware

app.add_middleware(
    CORSMiddleware,
    allow_origins=["https://app.example.com", "https://admin.example.com"],
    allow_credentials=True,
    allow_methods=["GET", "POST"],
    allow_headers=["*"],
)

方案 B — 保留通配符,禁用凭证 Option B: Keep Wildcard, Disable Credentials

如果 API 是真正的公开接口(无需认证),使用 * 是合理的,但必须将 allow_credentials 设为 false

If the API is genuinely public and requires no authentication, * is appropriate — but credentials must be disabled.

1
2
CORS_ALLOW_ORIGINS=*
CORS_ALLOW_CREDENTIALS=false
1
2
3
4
5
6
7
app.add_middleware(
    CORSMiddleware,
    allow_origins=["*"],
    allow_credentials=False,   # never True with wildcard
    allow_methods=["GET"],
    allow_headers=["*"],
)

检测方法 Detection

用以下 curl 命令模拟恶意源请求,观察响应头中 Access-Control-Allow-Origin 是否反射了请求的 Origin 值。

Use curl to simulate a malicious-origin request and check whether the response reflects the Origin.

1
2
3
4
5
6
7
curl -s -I -X OPTIONS "https://target.example.com/api/endpoint" \
  -H "Origin: https://evil.com" \
  -H "Access-Control-Request-Method: GET"

# 危险信号 Danger signals:
# Access-Control-Allow-Origin: https://evil.com   ← origin was reflected (not *)
# Access-Control-Allow-Credentials: true           ← credentials enabled

Key Takeaways

  • 同源策略(SOP) 是浏览器的默认保护;CORS 是有选择地放开这层保护的机制。 Same-Origin Policy is the browser’s default; CORS is the mechanism to selectively relax it.

  • CORS_ALLOW_ORIGINS=* 单独使用是可接受的 — 浏览器在通配符模式下不发送 Cookie,请求是匿名的。 Wildcard alone is acceptable — the browser does not send cookies in wildcard mode, so requests are anonymous.

  • * + CORS_ALLOW_CREDENTIALS=true 是 CORS 规范明确禁止的组合 — 部分框架不遵守规范,而是将请求的 Origin 反射回去,导致浏览器认为这是合法的特定来源授权,凭证被允许发出。 * + credentials=true is explicitly prohibited by spec — but some frameworks silently reflect the requesting origin, causing the browser to treat it as legitimate and forward credentials.

  • 攻击后果 — 任何网站都可以代表已登录用户向受害服务器发起认证请求并读取响应,等同于跨站会话劫持。 Attack consequence: any site can make authenticated requests to the victim server on behalf of a logged-in user and read the response — equivalent to cross-site session hijacking.

  • 修复 — 将 CORS_ALLOW_ORIGINS 替换为具体的可信来源列表;或若需要通配符则禁用凭证。两者不可同时使用。 Fix: replace the wildcard with a specific trusted origin list, or disable credentials if a wildcard is genuinely needed. Never combine both.


References

This post is licensed under CC BY 4.0 by the author.

Comments powered by Disqus.