CORS Security

CORS(Cross-Origin Resource Sharing,跨源资源共享)是现代浏览器实现的一种安全机制, 它控制 Web 应用程序如何向与提供原始页面的域不同的 域发起 HTTP 请求,以受控的方式放宽了 Same-Origin Policy (SOP)—— 一项基本的安全策略,它限制某一源的脚本访问 另一源的资源。尽管 CORS 对于现代 Web 应用程序架构至关重要,其中 前端经常需要消费托管在不同域上的 API,但其不当 配置是当代 Web 应用程序中最常见、最危险的漏洞之一。 CORS 配置错误可能将敏感数据暴露给未经授权的域, 即使存在反 CSRF 令牌也能允许跨站请求伪造 (CSRF) 攻击, 助长凭据窃取,并在极端情况下允许攻击者代表已认证用户执行 特权操作。问题因以下事实而加剧:许多 开发人员在开发过程中遇到 CORS 错误时,会选择过于宽松的 解决方案(例如使用通配符 "*" 或自动反射请求的源),而未能充分理解 其安全影响。本文深入探讨 CORS 的基础知识、 常见的配置漏洞,并确立在不同平台和框架上进行安全实现的 稳健实践,在功能性与适当的防御态势之间取得平衡。

Same-Origin Policy (SOP)

浏览器实现 SOP:脚本只能访问同源的资源 (协议 + 域名 + 端口)。CORS 以受控的方式放宽 SOP。

      # 同源
      https://example.com/api ← https://example.com/app [OK]
      # 不同源(被 SOP 阻止)
      https://example.com ← http://example.com (协议)
      https://example.com ← https://api.example.com (子域名)
      https://example.com ← https://example.com:8080 (端口)
      

CORS Headers

Access-Control-Allow-Origin

      # 允许特定源(推荐)
      Access-Control-Allow-Origin: https://trusted.com
      # 允许任何源(危险!)
      Access-Control-Allow-Origin: *
      # 基于白名单的动态配置(正确)
      const allowedOrigins = ['https://app1.com', 'https://app2.com'];
      const origin = request.headers.origin;
      if (allowedOrigins.includes(origin)) {
      res.setHeader('Access-Control-Allow-Origin', origin);
      }
      

其他重要的 Headers

      # 允许凭据(cookies、auth headers)
      Access-Control-Allow-Credentials: true
      # 允许的 HTTP 方法
      Access-Control-Allow-Methods: GET, POST, PUT, DELETE
      # 请求中允许的 headers
      Access-Control-Allow-Headers: Content-Type, Authorization, X-Requested-With
      # 向客户端 JavaScript 暴露的 headers
      Access-Control-Expose-Headers: X-Custom-Header, X-Request-Id
      # 预检请求的缓存时间(秒)
      Access-Control-Max-Age: 86400
      

Preflight Requests

浏览器在发起“非简单”请求之前会发送 OPTIONS 请求以验证权限。

      # 客户端发送预检请求
      OPTIONS /api/resource HTTP/1.1
      Origin: https://app.com
      Access-Control-Request-Method: DELETE
      Access-Control-Request-Headers: Authorization
      # 服务器返回权限响应
      HTTP/1.1 204 No Content
      Access-Control-Allow-Origin: https://app.com
      Access-Control-Allow-Methods: GET, POST, DELETE
      Access-Control-Allow-Headers: Authorization
      Access-Control-Max-Age: 86400
      

常见的 CORS 漏洞

1. 通配符与凭据并用

      # [错误] 易受攻击——无效且危险
      Access-Control-Allow-Origin: *
      Access-Control-Allow-Credentials: true
      # 浏览器会阻止此组合
      # [OK] 正确——特定源配合凭据
      Access-Control-Allow-Origin: https://trusted.com
      Access-Control-Allow-Credentials: true
      

2. Reflection Attack

      # [错误] 易受攻击——反射任意 origin
      const origin = request.headers.origin;
      res.setHeader('Access-Control-Allow-Origin', origin);
      res.setHeader('Access-Control-Allow-Credentials', 'true');
      # [OK] 正确——白名单校验
      const allowedOrigins = ['https://app.com', 'https://admin.com'];
      const origin = request.headers.origin;
      if (allowedOrigins.includes(origin)) {
      res.setHeader('Access-Control-Allow-Origin', origin);
      res.setHeader('Access-Control-Allow-Credentials', 'true');
      }
      

3. Subdomain Wildcard

      # [错误] 易受攻击——regex 实现不当
      const origin = request.headers.origin;
      if (/https:\/\/.*\.example\.com/.test(origin)) {
      res.setHeader('Access-Control-Allow-Origin', origin);
      }
      // 会接受 https://evil.example.com.attacker.com
      # [OK] 正确——严格校验
      const origin = request.headers.origin;
      if (/^https:\/\/[a-z0-9-]+\.example\.com$/.test(origin)) {
      res.setHeader('Access-Control-Allow-Origin', origin);
      }
      

按技术划分的安全配置

Node.js/Express (CORS middleware)

      const cors = require('cors');
      // 安全配置
      const corsOptions = {
      origin: function (origin, callback) {
      const allowedOrigins = [
      'https://app.example.com',
      'https://admin.example.com'
      ];
      if (!origin || allowedOrigins.includes(origin)) {
      callback(null, true);
      } else {
      callback(new Error('Not allowed by CORS'));
      }
      },
      credentials: true,
      methods: ['GET', 'POST', 'PUT', 'DELETE'],
      allowedHeaders: ['Content-Type', 'Authorization'],
      maxAge: 86400
      };
      app.use(cors(corsOptions));
      

Nginx

      # 条件配置
      map $http_origin $cors_origin {
      default "";
      "~^https://app\\.example\\.com$" $http_origin;
      "~^https://admin\\.example\\.com$" $http_origin;
      }
      server {
      location /api {
      if ($cors_origin != "") {
      add_header Access-Control-Allow-Origin $cors_origin always;
      add_header Access-Control-Allow-Credentials true always;
      add_header Access-Control-Allow-Methods "GET, POST, PUT, DELETE" always;
      add_header Access-Control-Allow-Headers "Authorization, Content-Type" always;
      }
      if ($request_method = OPTIONS) {
      return 204;
      }
      }
      }
      

Apache

      # .htaccess
      SetEnvIf Origin "^https://(app|admin)\\.example\\.com$" CORS_ORIGIN=$0
      Header always set Access-Control-Allow-Origin "%e" env=CORS_ORIGIN
      Header always set Access-Control-Allow-Credentials "true" env=CORS_ORIGIN
      Header always set Access-Control-Allow-Methods "GET, POST, PUT, DELETE" env=CORS_ORIGIN
      Header always set Access-Control-Allow-Headers "Authorization, Content-Type" env=CORS_ORIGIN
      # 响应 OPTIONS 预检请求
      RewriteEngine On
      RewriteCond % OPTIONS
      RewriteRule ^(.*)$ $1 [R=204,L]
      

Testing CORS

      # 使用 curl 测试
      curl -H "Origin: https://evil.com" \\
      -H "Access-Control-Request-Method: DELETE" \\
      -H "Access-Control-Request-Headers: Authorization" \\
      -X OPTIONS \\
      https://api.example.com/resource
      # JavaScript 测试
      fetch('https://api.example.com/data', {
      method: 'GET',
      credentials: 'include',
      headers: {
      'Content-Type': 'application/json'
      }
      }).then(response => console.log(response));
      

Best Practices

  • 切勿在包含敏感数据的 API 中使用通配符 (*)
  • 明确的白名单列出允许的源
  • 严格校验源,使用安全的 regex
  • 尽量减少 credentials:仅在确实必要时启用
  • Least privilege(最小权限):仅允许必要的方法和 headers
  • Cache preflight(缓存预检):使用 Max-Age 以减少开销
  • Monitoring(监控):记录可疑的 CORS 拒绝

安全 CORS 检查清单

  • [OK] 源已对照明确的白名单进行校验
  • [OK] 校验 regex 不允许绕过
  • [OK] 仅在必要时启用 Credentials
  • [OK] 方法和 headers 限制到最低限度
  • [OK] 预检请求配置正确
  • [OK] 已针对恶意源进行测试
  • [OK] 监控被拒绝请求的日志