CORSAPI SecurityWeb Security

CORS Misconfiguration: The Security Risks of Wildcard Origins

WebSentry Team
· · 7 min read

What CORS Actually Does

Cross-Origin Resource Sharing (CORS) controls which websites can make requests to your server. Without it, browsers enforce the same-origin policy — scripts on evil.com can't read responses from yourapi.com.

CORS relaxes this restriction by allowing your server to declare which origins are trusted. The problem is that many developers misconfigure CORS, effectively disabling this protection.

The Dangerous Configurations

1. Wildcard Origin with Credentials

The most common and dangerous misconfiguration:

Access-Control-Allow-Origin: *
Access-Control-Allow-Credentials: true

Browsers actually block this combination — you can't use * with credentials. But some developers work around it by reflecting the request's Origin header:

// DANGEROUS — do not do this
res.setHeader('Access-Control-Allow-Origin', req.headers.origin);
res.setHeader('Access-Control-Allow-Credentials', 'true');

This means any website can make authenticated requests to your API and read the response. An attacker's site could steal user data, modify account settings, or perform actions as the logged-in user.

2. Reflecting Origin Without Validation

Sometimes the Origin header is reflected directly without checking against an allowlist:

// DANGEROUS
const origin = req.headers['origin'];
res.setHeader('Access-Control-Allow-Origin', origin);

Always validate the origin against a known list of trusted domains.

3. Null Origin

Access-Control-Allow-Origin: null

The null origin can be triggered from sandboxed iframes and local files. Allowing it opens your API to attacks from those contexts.

4. Overly Broad Wildcards

// Intended: allow all subdomains of example.com
// Bug: also matches evil-example.com
if (origin.endsWith('example.com')) {
  res.setHeader('Access-Control-Allow-Origin', origin);
}

Substring matching on origins is a common bug. Always match the full origin including the protocol and port.

Secure CORS Configuration

Explicit Allowlist

const ALLOWED_ORIGINS = new Set([
  'https://myapp.com',
  'https://admin.myapp.com',
  'https://staging.myapp.com',
]);

const origin = req.headers['origin'];
if (ALLOWED_ORIGINS.has(origin)) {
  res.setHeader('Access-Control-Allow-Origin', origin);
  res.setHeader('Vary', 'Origin');
}

Important Headers

  • Vary: Origin — Always include this when the ACAO header changes based on the request. Without it, CDN caches may serve the wrong origin to the wrong requester.
  • Access-Control-Max-Age — Cache preflight responses to reduce OPTIONS requests: Access-Control-Max-Age: 7200
  • Access-Control-Allow-Methods — Only list methods you actually support
  • Access-Control-Allow-Headers — Only list headers you actually need

When Wildcard Is OK

Access-Control-Allow-Origin: * is fine for truly public, read-only resources:

  • Public CDN assets (fonts, images, CSS)
  • Public APIs that don't require authentication
  • Open data endpoints

The key rule: never combine * with Access-Control-Allow-Credentials: true, and never reflect origins without validation when credentials are involved.

Testing Your CORS Configuration

You can test with curl:

curl -I -H "Origin: https://evil.com" https://yourapi.com/endpoint

Check whether the response includes Access-Control-Allow-Origin: https://evil.com. If it does, your API is vulnerable.

Run a WebSentry scan to automatically check your site's CORS configuration and catch dangerous misconfigurations before attackers do.

Check Your Website's Security

Run a free security scan and get your A-F grade in seconds.

Scan Your Site Free