作者:闫乾苓
一、背景:CORS 漏洞为何值得关注?
跨域资源共享(CORS, Cross-Origin Resource Sharing)是现代 Web 开发中绕不开的核心机制。它通过放宽浏览器的同源策略(Same-Origin Policy),允许前端应用从一个源(Origin)安全地请求另一个源的资源。然而,若配置不当,CORS 反而会成为数据泄露的入口。
在一次安全评估中,我们发现目标系统存在一个典型的 CORS 配置缺陷:
请求头:Origin: https://www.baidu.com
响应头:Access-Control-Allow-Origin: https://www.baidu.com
这意味着:任意第三方网站(如百度)均可通过前端 JavaScript 跨域读取该接口的敏感数据。虽然该漏洞被定为“低危”,但若结合其他漏洞(如身份认证缺陷),仍可能造成严重后果。
二、漏洞原理简析
2.1 什么是“动态反射 Origin”?
许多开发者为了“方便”,在服务端直接将请求头中的 Origin 值原样写入响应头:
Access-Control-Allow-Origin: <请求中的 Origin>
这种做法看似“灵活”,实则完全放弃了访问控制——任何网站都能跨域访问你的 API。
2.2 为什么不能用 *?
虽然 Access-Control-Allow-Origin: * 可以允许所有源访问,但它禁止携带凭证(如 Cookie、Authorization)。一旦你的 API 需要身份认证,就必须指定具体源,而不能使用通配符。
三、安全修复方案:基于 Nginx 的白名单机制
我们采用 Nginx + 白名单映射 的方式,实现安全、高效、可维护的 CORS 策略。
3.1 核心思路
- 拒绝动态反射:绝不直接回显 Origin。
- 启用白名单:仅允许预定义的可信源跨域访问。
- 区分预检与实际请求:正确处理 OPTIONS 请求。
3.2 配置步骤详解
步骤 1:创建白名单映射文件
创建 /etc/nginx/conf.d/cors_whitelist.conf:
map $http_origin $cors_origin {
default "";
# 可信源列表(必须完整、精确)
"https://www.yourdomain.com" "https://www.yourdomain.com";
"https://app.yourdomain.com" "https://app.yourdomain.com";
"http://192.168.61.11" "http://192.168.61.11"; # 开发环境 IP
"http://192.168.61.11:8080" "http://192.168.61.11:8080"; # 带端口的前端
}
关键点:
- 白名单必须包含协议 + 域名/IP + 端口(非默认端口时)
- default “” 表示不在白名单中的 Origin 不返回 Access-Control-Allow-Origin 头
步骤 2:创建通用 CORS 响应头配置
创建 /etc/nginx/conf.d/cors.conf:
add_header Access-Control-Allow-Origin $cors_origin always;
add_header Access-Control-Allow-Methods "GET, POST, OPTIONS, PUT, DELETE" always;
add_header Access-Control-Allow-Headers "DNT,User-Agent,X-Requested-With,Content-Type,Authorization" always;
# add_header Access-Control-Allow-Credentials "true" always; #若 API 需要携带 Cookie / Token, 按需启用
注意:
- always 参数确保即使返回 4xx/5xx 也包含 CORS 头,避免前端调试困难。
- 若 API 需要携带 Cookie / Token,必须设置 Access-Control-Allow-Credentials: true,并在白名单场景下加上 add_header Access-Control-Allow-Credentials “true” always; “启用Access-Control-Allow-Credentials: true时,前端请求需设置withCredentials: true(XMLHttpRequest/fetch)”,避免因前后端配置不匹配导致凭证携带失败。
步骤 3:在 Server 块中集成配置
server {
listen 443 ssl;
server_name xxxx.xxxx.com.cn;
location /xxx/api/xxx {
# 拒绝非白名单 Origin
if ($http_origin != "" && $cors_origin = "") {
return 403;
}
# 处理预检请求(OPTIONS)
if ($request_method = 'OPTIONS') {
add_header Access-Control-Allow-Origin $cors_origin;
add_header Access-Control-Allow-Methods "GET, POST, OPTIONS, PUT, DELETE";
add_header Access-Control-Allow-Headers "DNT,User-Agent,X-Requested-With,Content-Type,Authorization";
# add_header Access-Control-Allow-Credentials "true"; # #若 API 需要携带 Cookie / Token, 按需启用
add_header Access-Control-Max-Age 1728000; # 缓存 20 天
add_header Content-Length 0;
return 204; # 必须返回 204,无响应体
}
# 加载通用 CORS 头(用于实际请求)
include conf.d/cors.conf;
proxy_pass http://backend_server;
}
}
四、技术细节:为什么这样配置?
4.1 预检请求(Preflight)处理
当请求满足以下任一条件时,浏览器会先发 OPTIONS 预检请求:
- 使用 PUT、DELETE 等非简单方法
- 设置自定义头(如 Authorization)
- Content-Type 为 application/json
Nginx 必须在 OPTIONS 响应中明确允许这些操作,否则浏览器会直接拦截后续请求。
4.2 Access-Control-Max-Age 的作用
设置 Access-Control-Max-Age: 1728000(20 天)可让浏览器缓存预检结果,避免每次请求都发 OPTIONS,显著提升性能。
开发阶段建议设为 10 秒,便于调试。
4.3 为何 return 204?
- 204 No Content 表示“成功但无响应体”
- 必须避免返回 HTML 或 JSON,否则浏览器可能认为预检失败
- return 204 会立即终止请求处理,跳过后续 proxy_pass 等逻辑
五、测试验证:确保修复有效
5.1 验证白名单内源
curl -H “Origin: https://app.yourdomain.com” -I https://xxxx.xxxx.com.cn:443/xxx/api/xxx
预期响应头:
Access-Control-Allow-Origin: https://app.yourdomain.com
5.2 验证白名单外源
curl -H “Origin: https://www.baidu.com” -I https://xxxx.xxxx.com.cn:443/xxx/api/xxx
预期结果:无 Access-Control-Allow-Origin 头
→ 浏览器将拦截响应,前端无法读取数据。
5.3 验证 OPTIONS 预检
curl -X OPTIONS \
-H "Origin: https://app.yourdomain.com" \
-H "Access-Control-Request-Method: POST" \
-H "Access-Control-Request-Headers: Authorization" \
-I https://xxxx.xxxx.com.cn:443/xxx/api/xxx
预期:返回 204,且包含完整的 Allow-Origin、Allow-Methods、Allow-Headers。
六、上线建议
- 先在测试环境验证:确保所有业务前端域名/IP 都已加入白名单。
- 申请割接窗口:避免因遗漏白名单导致线上业务中断。
- 监控日志:上线后观察是否有大量 OPTIONS 请求或 CORS 错误。
七、总结
CORS 本身不是漏洞,错误的配置才是。通过 Nginx 的 map 模块实现白名单机制,既能满足业务跨域需求,又能有效防范未授权访问。安全无小事,即使是“低危”漏洞,也值得我们以严谨的态度对待。
安全原则:永远不要信任客户端输入,包括 Origin 头。
附:参考配置文件结构
/etc/nginx/
├── nginx.conf
└── conf.d/
├── cors_whitelist.conf # 白名单映射
└── cors.conf # CORS 响应头
通过模块化配置,未来增删可信源只需修改 cors_whitelist.conf,无需改动主逻辑,提升可维护性与安全性。
浙公网安备 33010602011771号