CORS Security

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