CORS跨域安全策略
约 1375 字大约 5 分钟
corssecurity
2025-08-11
概述
跨源资源共享(Cross-Origin Resource Sharing, CORS)是浏览器的安全机制,用于控制网页如何从不同源(协议 + 域名 + 端口)请求资源。CORS 配置不当是 Web 应用中最常见的安全问题之一。
同源策略基础
浏览器的同源策略(Same-Origin Policy)限制了不同源之间的交互。所谓"同源"要求协议、域名、端口三者完全一致。
简单请求与预检请求
简单请求的条件
满足以下所有条件的请求不会触发预检:
- 方法为 GET、HEAD 或 POST
- 仅包含安全头部(Accept、Accept-Language、Content-Language、Content-Type)
- Content-Type 限于
text/plain、multipart/form-data、application/x-www-form-urlencoded
CORS 响应头详解
# Nginx CORS 配置示例
location /api/ {
# 指定允许的源(不要用 * 配合 credentials)
add_header 'Access-Control-Allow-Origin' 'https://app.example.com' always;
# 允许携带 cookies/认证信息
add_header 'Access-Control-Allow-Credentials' 'true' always;
# 暴露给前端的响应头
add_header 'Access-Control-Expose-Headers' 'X-Request-Id, X-Total-Count' always;
# 预检请求处理
if ($request_method = 'OPTIONS') {
add_header 'Access-Control-Allow-Origin' 'https://app.example.com';
add_header 'Access-Control-Allow-Methods' 'GET, POST, PUT, DELETE, OPTIONS';
add_header 'Access-Control-Allow-Headers' 'Authorization, Content-Type, X-Requested-With';
add_header 'Access-Control-Max-Age' '86400'; # 预检缓存 24 小时
add_header 'Content-Length' '0';
return 204;
}
}动态 Origin 白名单
# Python/FastAPI 示例
from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware
app = FastAPI()
ALLOWED_ORIGINS = [
"https://app.example.com",
"https://staging.example.com",
"http://localhost:3000", # 仅开发环境
]
app.add_middleware(
CORSMiddleware,
allow_origins=ALLOWED_ORIGINS, # 白名单
allow_credentials=True,
allow_methods=["GET", "POST", "PUT", "DELETE"],
allow_headers=["Authorization", "Content-Type"],
expose_headers=["X-Request-Id"],
max_age=3600,
)// Node.js/Express 示例
const cors = require('cors');
const allowedOrigins = [
'https://app.example.com',
'https://staging.example.com',
];
app.use(cors({
origin: (origin, callback) => {
// 允许无 origin 的请求(如移动端或服务端)
if (!origin) return callback(null, true);
if (allowedOrigins.includes(origin)) {
callback(null, true);
} else {
callback(new Error('Not allowed by CORS'));
}
},
credentials: true,
methods: ['GET', 'POST', 'PUT', 'DELETE'],
allowedHeaders: ['Authorization', 'Content-Type'],
exposedHeaders: ['X-Request-Id'],
maxAge: 3600,
}));带 Credentials 的跨域请求
当请求需要携带 cookies 或 Authorization 头时,需要特殊处理。
// 前端必须设置 credentials
fetch('https://api.example.com/data', {
method: 'GET',
credentials: 'include', // 携带 cookies
headers: {
'Authorization': 'Bearer token123'
}
});
// XMLHttpRequest
const xhr = new XMLHttpRequest();
xhr.withCredentials = true;关键规则:当 Access-Control-Allow-Credentials: true 时,Access-Control-Allow-Origin 不能为通配符 *,必须指定确切的源。
常见安全误配置
误配置 1:反射 Origin(最危险)
# 危险:直接反射请求中的 Origin
@app.middleware("http")
async def cors_middleware(request, call_next):
response = await call_next(request)
origin = request.headers.get("origin", "")
# 把任意 origin 原样返回 — 等于没有 CORS 保护
response.headers["Access-Control-Allow-Origin"] = origin
response.headers["Access-Control-Allow-Credentials"] = "true"
return response攻击者可以从任意恶意网站读取用户在目标站点的数据。
误配置 2:正则匹配不当
# 危险:后缀匹配允许攻击者注册 evil-example.com
ALLOWED = r".*\.example\.com" # 正确
ALLOWED = r".*example\.com" # 错误!匹配 evil-example.com
# 更安全的做法
import re
def is_origin_allowed(origin: str) -> bool:
pattern = r"^https://([a-z0-9-]+\.)?example\.com$"
return bool(re.match(pattern, origin))误配置 3:允许 null Origin
# 危险:null origin 可被沙箱 iframe 伪造
if origin == "null":
allow = True # 不安全!误配置 4:通配符 + Credentials
# 浏览器会直接拒绝这个响应
Access-Control-Allow-Origin: *
Access-Control-Allow-Credentials: true
# 但有些服务器框架可能不阻止这种配置CORS 与 Cookie SameSite 的交互
# 跨域 Cookie 的正确设置
response.set_cookie(
key="session_id",
value="abc123",
httponly=True,
secure=True, # 必须 HTTPS
samesite="None", # 允许跨域发送
domain=".example.com",
max_age=3600,
)测试与验证
# 测试预检请求
curl -X OPTIONS https://api.example.com/data \
-H "Origin: https://evil.com" \
-H "Access-Control-Request-Method: PUT" \
-H "Access-Control-Request-Headers: Authorization" \
-v
# 检查响应头是否泄露
# 如果 Access-Control-Allow-Origin 返回了 https://evil.com
# 说明存在 origin 反射漏洞
# 使用 securityheaders.com 在线检测
# 使用 OWASP ZAP 扫描 CORS 配置最佳实践总结
- 使用严格的 Origin 白名单,不要反射任意 Origin
- 生产环境禁止
Access-Control-Allow-Origin: *,尤其是带 credentials 的场景 - 预检请求设置合理的 Max-Age(如 3600 秒),减少 OPTIONS 请求次数
- 正则匹配 Origin 时注意锚定,防止子域名绕过
- 不允许 null Origin,它可以被沙箱 iframe 伪造
- 最小化 Allow-Methods 和 Allow-Headers,只开放必需的
- 配合 SameSite Cookie 属性提供纵深防御
- 定期审计 CORS 配置,使用自动化工具检测错误配置
贡献者
更新日志
2026/3/14 13:09
查看所有更新日志
9f6c2-feat: organize wiki content and refresh site setup于