Skip to content

CWE-942: Overly Permissive CORS

Overview

Overly permissive Cross-Origin Resource Sharing (CORS) occurs when a web application allows requests from any origin or from untrusted origins, exposing sensitive resources to unauthorized websites. This can lead to data theft, account compromise, and cross-origin attacks.

OWASP Classification

A02:2025 - Security Misconfiguration

Risk

High: Attackers can read sensitive data, perform actions as authenticated users, or exfiltrate information by abusing overly broad CORS policies. This impacts confidentiality and integrity of user data and backend APIs.

Remediation Strategy

Restrict Allowed Origins (Primary Defense)

  • Only allow trusted, specific origins in CORS configuration (never use * in production)
  • Use allowlists for known domains
  • Avoid reflecting the Origin header dynamically

Limit Allowed Methods and Headers

  • Only permit required HTTP methods (GET, POST, etc.)
  • Restrict allowed headers to the minimum necessary
  • Disallow credentials unless absolutely required

Disable Credentials by Default

  • Set Access-Control-Allow-Credentials: false unless cross-origin credentials are needed
  • Never use Access-Control-Allow-Origin: * with credentials

Monitor and Audit CORS Usage

  • Log CORS requests and monitor for unexpected origins
  • Regularly review and test CORS policy effectiveness

Remediation Steps

Core principle: Never extend browser trust to unintended origins; cross-origin access must be explicitly limited to a small, well-defined set of trusted origins and enforced consistently by design. CORS must be restrictive: allowlist origins and never use wildcard with credentials.

Identify Overly Permissive CORS Configuration

When reviewing security scan results:

  • Check response headers: Look for Access-Control-Allow-Origin: *
  • Review CORS middleware: Find origin reflection patterns
  • Check credentials: Identify Access-Control-Allow-Credentials: true with wildcards
  • Review allowed methods: Find Access-Control-Allow-Methods: *
  • Check preflight handling: Look for overly permissive OPTIONS responses

Vulnerable patterns:

// Express.js
app.use((req, res, next) => {
  res.header('Access-Control-Allow-Origin', '*');  // Allows any origin!
  res.header('Access-Control-Allow-Credentials', 'true');  // Dangerous combo!
  next();
});

// Origin reflection
res.header('Access-Control-Allow-Origin', req.headers.origin);  // Trusts any origin!

// Wildcard methods
res.header('Access-Control-Allow-Methods', '*');  // Allows all HTTP methods!

Restrict Allowed Origins with Allowlist (Primary Defense)

// VULNERABLE - wildcard origin
app.use((req, res, next) => {
  res.setHeader('Access-Control-Allow-Origin', '*');
  // Any website can read responses!
  next();
});

// VULNERABLE - reflecting origin without validation
app.use((req, res, next) => {
  const origin = req.headers.origin;
  res.setHeader('Access-Control-Allow-Origin', origin);
  // Trusts any origin - same as wildcard!
  next();
});

// SECURE - allowlist specific origins
const ALLOWED_ORIGINS = [
  'https://app.example.com',
  'https://www.example.com',
  'https://mobile.example.com'
];

app.use((req, res, next) => {
  const origin = req.headers.origin;

  if (ALLOWED_ORIGINS.includes(origin)) {
    res.setHeader('Access-Control-Allow-Origin', origin);
  }
  // No CORS header = browser blocks request from untrusted origins

  next();
});

// Better - with strict validation
function isAllowedOrigin(origin) {
  if (!origin) return false;

  // Exact match
  if (ALLOWED_ORIGINS.includes(origin)) {
    return true;
  }

  // Pattern match for subdomains (use carefully!)
  const allowedPattern = /^https:\/\/([a-z0-9-]+\.)?example\.com$/;
  return allowedPattern.test(origin);
}

app.use((req, res, next) => {
  const origin = req.headers.origin;

  if (isAllowedOrigin(origin)) {
    res.setHeader('Access-Control-Allow-Origin', origin);
  } else if (origin) {
    console.warn(`Blocked CORS request from: ${origin}`);
  }

  next();
});

Python (Flask):

from flask import Flask, request, make_response

ALLOWED_ORIGINS = {
    'https://app.example.com',
    'https://www.example.com'
}

@app.after_request
def add_cors_headers(response):
    origin = request.headers.get('Origin')

    if origin in ALLOWED_ORIGINS:
        response.headers['Access-Control-Allow-Origin'] = origin
    # Else: no CORS header, browser blocks request

    return response

Limit Allowed Methods and Headers

// VULNERABLE - allows all methods and headers
app.options('*', (req, res) => {
  res.setHeader('Access-Control-Allow-Methods', '*');
  res.setHeader('Access-Control-Allow-Headers', '*');
  res.send();
});

// SECURE - specific methods and headers only
const ALLOWED_METHODS = ['GET', 'POST', 'PUT', 'DELETE'];
const ALLOWED_HEADERS = ['Content-Type', 'Authorization', 'X-API-Key'];

app.options('*', (req, res) => {
  const origin = req.headers.origin;

  if (ALLOWED_ORIGINS.includes(origin)) {
    res.setHeader('Access-Control-Allow-Origin', origin);
    res.setHeader('Access-Control-Allow-Methods', ALLOWED_METHODS.join(', '));
    res.setHeader('Access-Control-Allow-Headers', ALLOWED_HEADERS.join(', '));
    res.setHeader('Access-Control-Max-Age', '86400');  // Cache preflight for 24h
  }

  res.status(204).send();
});

// Apply same restrictions to actual requests
app.use((req, res, next) => {
  const origin = req.headers.origin;

  if (ALLOWED_ORIGINS.includes(origin)) {
    res.setHeader('Access-Control-Allow-Origin', origin);
    res.setHeader('Access-Control-Expose-Headers', 'X-Total-Count');
  }

  next();
});

Handle Credentials Securely

// VULNERABLE - credentials with wildcard
res.setHeader('Access-Control-Allow-Origin', '*');
res.setHeader('Access-Control-Allow-Credentials', 'true');
// INVALID - browsers reject this combo!

// VULNERABLE - credentials with reflected origin
res.setHeader('Access-Control-Allow-Origin', req.headers.origin);
res.setHeader('Access-Control-Allow-Credentials', 'true');
// Any origin can read authenticated responses!

// SECURE - credentials only with specific origin
const origin = req.headers.origin;

if (ALLOWED_ORIGINS.includes(origin)) {
  res.setHeader('Access-Control-Allow-Origin', origin);

  // Only enable credentials if truly needed
  if (requiresCredentials(req.path)) {
    res.setHeader('Access-Control-Allow-Credentials', 'true');
  }
}

// Public APIs - no credentials needed
app.get('/api/public/*', (req, res) => {
  res.setHeader('Access-Control-Allow-Origin', '*');
  // No credentials header - cookies won't be sent
  res.json({ public: 'data' });
});

// Authenticated APIs - strict origin check
app.get('/api/user/*', authenticate, (req, res) => {
  const origin = req.headers.origin;

  if (ALLOWED_ORIGINS.includes(origin)) {
    res.setHeader('Access-Control-Allow-Origin', origin);
    res.setHeader('Access-Control-Allow-Credentials', 'true');
    res.json({ user: req.user });
  } else {
    res.status(403).json({ error: 'Forbidden' });
  }
});

Monitor and Audit CORS Requests

// Logging CORS requests
app.use((req, res, next) => {
  const origin = req.headers.origin;

  if (origin) {
    const allowed = ALLOWED_ORIGINS.includes(origin);

    console.log({
      timestamp: new Date().toISOString(),
      method: req.method,
      path: req.path,
      origin: origin,
      allowed: allowed,
      ip: req.ip
    });

    // Alert on blocked CORS attempts
    if (!allowed && req.method === 'OPTIONS') {
      alertSecurityTeam({
        event: 'Blocked CORS Preflight',
        origin: origin,
        target: req.path,
        ip: req.ip
      });
    }
  }

  next();
});

// Metrics tracking
const corsMetrics = {
  allowed: 0,
  blocked: 0,
  origins: new Map()
};

function trackCORS(origin, allowed) {
  if (allowed) {
    corsMetrics.allowed++;
  } else {
    corsMetrics.blocked++;
  }

  const count = corsMetrics.origins.get(origin) || 0;
  corsMetrics.origins.set(origin, count + 1);
}

// Periodic audit
setInterval(() => {
  console.log('CORS Metrics:', {
    allowed: corsMetrics.allowed,
    blocked: corsMetrics.blocked,
    topOrigins: Array.from(corsMetrics.origins.entries())
      .sort((a, b) => b[1] - a[1])
      .slice(0, 10)
  });
}, 3600000);  // Every hour

Test CORS Configuration

// Test script
const axios = require('axios');

async function testCORS() {
  // Test 1: Allowed origin
  try {
    const res1 = await axios.get('https://api.example.com/data', {
      headers: { 'Origin': 'https://app.example.com' }
    });
    console.log('✓ Allowed origin accepted:', 
                res1.headers['access-control-allow-origin']);
  } catch (e) {
    console.error('✗ Allowed origin rejected');
  }

  // Test 2: Blocked origin
  try {
    const res2 = await axios.get('https://api.example.com/data', {
      headers: { 'Origin': 'https://evil.com' }
    });
    if (res2.headers['access-control-allow-origin']) {
      console.error('✗ Evil origin was allowed!');
    } else {
      console.log('✓ Evil origin blocked');
    }
  } catch (e) {
    console.log('✓ Evil origin blocked');
  }

  // Test 3: Credentials handling
  const res3 = await axios.options('https://api.example.com/user', {
    headers: { 'Origin': 'https://app.example.com' }
  });

  if (res3.headers['access-control-allow-credentials'] === 'true' &&
      res3.headers['access-control-allow-origin'] !== '*') {
    console.log('✓ Credentials properly configured');
  }

  // Test 4: Methods restriction
  const allowedMethods = res3.headers['access-control-allow-methods'];
  if (allowedMethods !== '*') {
    console.log('✓ Methods are restricted:', allowedMethods);
  } else {
    console.error('✗ All methods allowed');
  }
}

testCORS();

Browser testing:

// In browser console on https://evil.com
fetch('https://api.example.com/data', {
  credentials: 'include'
})
.then(r => r.json())
.then(data => console.log('Data leaked:', data))
.catch(e => console.log('Correctly blocked:', e));

// Should see CORS error:
// Access to fetch at 'https://api.example.com/data' from origin 
// 'https://evil.com' has been blocked by CORS policy

Dynamic Scan Guidance

For guidance on remediating this CWE when detected by dynamic (DAST) scanners:

Common Vulnerable Patterns

  • Using Access-Control-Allow-Origin: * in production
  • Reflecting the Origin header without validation
  • Allowing all methods and headers
  • Enabling credentials for all origins

Wildcard CORS Policy (JavaScript Express)

// Express.js example
app.use((req, res, next) => {
  res.header('Access-Control-Allow-Origin', '*'); // Dangerous: allows any origin
  next();
});

Why this is vulnerable: Using Access-Control-Allow-Origin: * allows any website to make cross-origin requests to your API and read the responses, enabling malicious sites to steal sensitive user data, perform authenticated actions on behalf of users, or exfiltrate private information to attacker-controlled domains.

Secure Patterns

Allowlist-Based CORS Policy (JavaScript Express)

const allowedOrigins = ['https://trusted.example.com', 'https://app.example.com'];
app.use((req, res, next) => {
  const origin = req.headers.origin;
  if (allowedOrigins.includes(origin)) {
    res.header('Access-Control-Allow-Origin', origin);
    res.header('Access-Control-Allow-Credentials', 'true');
  }
  next();
});

Why this works:

  • Restricts cross-origin access to a pre-approved allowlist of trusted domains only
  • Prevents malicious websites from making authenticated requests to your API
  • Validates origin header against exact matches, preventing subdomain or protocol bypass
  • Enables credentials (cookies, auth headers) only for trusted origins, not wildcards
  • Protects sensitive data from being accessible to arbitrary third-party sites

Security Checklist

  • No Access-Control-Allow-Origin: * in production
  • Origins validated against allowlist (not reflected blindly)
  • Access-Control-Allow-Credentials: true only with specific origins
  • Methods restricted to required HTTP verbs only
  • Headers restricted to necessary headers only
  • Public vs authenticated endpoints have different CORS policies
  • CORS requests logged and monitored
  • Tested with both allowed and blocked origins
  • Tested credentials are not sent to untrusted origins

Additional Resources