CORS Misconfiguration Vulnerabilities — The Three That Actually Get Exploited
Updated April 2026
Reading this article? Verify your fix in real-time. Test your CORS config for vulnerabilities → CORSFixer
A CORS error means your config is too strict. A CORS misconfiguration means it is too loose — and attackers can exploit it to steal data. These three patterns account for most real-world CORS vulnerabilities.
Vulnerability 1 — Origin Reflection
The server reflects whatever origin the request sends back in the response. An attacker from evil.com sends a request — the server responds with Access-Control-Allow-Origin: https://evil.com.
# VULNERABLE — reflects any origin
app.use((req, res, next) => { res.setHeader("Access-Control-Allow-Origin", req.headers.origin); // ❌ res.setHeader("Access-Control-Allow-Credentials", "true"); next();
});
# SAFE — allowlist check
const ALLOWED = new Set(["https://yourapp.com", "https://staging.yourapp.com"]);
app.use((req, res, next) => { const origin = req.headers.origin; if (ALLOWED.has(origin)) { res.setHeader("Access-Control-Allow-Origin", origin); res.setHeader("Vary", "Origin"); } next();
});
Vulnerability 2 — Null Origin
Browsers send Origin: null for requests from sandboxed iframes, file:// URLs, and some redirects. Allowing null origin lets attackers use a sandboxed iframe to make credentialed requests.
# VULNERABLE
if (origin === "null") { res.setHeader("Access-Control-Allow-Origin", "null"); // ❌
}
# SAFE — never allowlist null
const ALLOWED = new Set(["https://yourapp.com"]);
if (ALLOWED.has(origin)) { // null is never in ALLOWED res.setHeader("Access-Control-Allow-Origin", origin);
}
Vulnerability 3 — Regex Bypass
Substring and suffix checks are bypassed by domains that match the pattern but are controlled by an attacker.
# VULNERABLE — substring check
if (origin.includes("yourapp.com")) { // ❌ evil-yourapp.com matches
# VULNERABLE — endsWith check
if (origin.endsWith("yourapp.com")) { // ❌ yourapp.com.evil.com matches
# SAFE — exact set membership
const ALLOWED = new Set([ "https://yourapp.com", "https://www.yourapp.com", "https://staging.yourapp.com",
]);
if (ALLOWED.has(origin)) { ... }
Test your API for these vulnerabilities
# Test origin reflection curl -s -I -H "Origin: https://evil.com" \ https://api.yourapp.com/endpoint | grep -i access-control-allow-origin # If it returns: Access-Control-Allow-Origin: https://evil.com → VULNERABLE # Test null origin curl -s -I -H "Origin: null" \ https://api.yourapp.com/endpoint | grep -i access-control-allow-origin # If it returns: Access-Control-Allow-Origin: null → VULNERABLE # Test regex bypass curl -s -I -H "Origin: https://evil-yourapp.com" \ https://api.yourapp.com/endpoint | grep -i access-control-allow-origin # If it returns that origin → VULNERABLE
The safe pattern for every stack
// The one correct CORS origin check
const ALLOWED_ORIGINS = new Set([ "https://yourapp.com", "https://staging.yourapp.com", // Never add: *, null, or anything dynamic
]);
function getCorsOrigin(requestOrigin) { return ALLOWED_ORIGINS.has(requestOrigin) ? requestOrigin : null;
} Test your CORS config for vulnerabilities → CORSFixer