CORStest: Hunting Misconfigured Cross-Origin Policies at Scale
Hook
Only 3% of the Alexa top 1 million websites had CORS enabled when researchers scanned them with CORStest—but among those 30,000 sites, a surprising number were leaking credentials across origins due to simple configuration mistakes.
Context
Cross-Origin Resource Sharing (CORS) was introduced to relax the Same-Origin Policy in a controlled way, allowing web applications to make legitimate cross-domain requests. But like most security mechanisms that trade convenience for protection, CORS is easy to misconfigure. A developer might reflexively echo back the Origin header to make their API "just work" during development, or use overly permissive wildcards that defeat the entire security model.
The problem compounds at scale. Manual CORS testing is tedious—you need to send requests with crafted Origin headers, analyze the Access-Control-Allow-Origin responses, check for credentials support, and repeat this across every endpoint. When James Kettle published his influential CORS research demonstrating real-world exploitation patterns, the security community needed a practical tool to detect these issues systematically. CORStest emerged from this need: a Python scanner built by researchers at Ruhr University Bochum to identify the most common CORS misconfigurations that actually matter in the wild.
Technical Insight
CORStest's architecture is refreshingly straightforward—it's a multiprocessing Python script that parallels HTTP requests to test domains against a battery of malicious Origin headers. The tool maintains a list of test cases representing real-world misconfiguration patterns: null origins, pre-domain wildcards, post-domain wildcards, subdomain allowances, and popular development origins like JSFiddle and CodePen. For each target domain, it spawns worker processes (32 by default) to send these crafted requests simultaneously.
Here's the core detection logic for one of the most dangerous patterns—reflecting arbitrary origins when credentials are allowed:
# Simplified example of CORStest's origin reflection test
import requests
def test_arbitrary_origin(target_url):
evil_origin = "https://attacker.com"
headers = {"Origin": evil_origin}
response = requests.get(target_url, headers=headers)
acao = response.headers.get("Access-Control-Allow-Origin")
acac = response.headers.get("Access-Control-Allow-Credentials")
if acao == evil_origin and acac == "true":
return "CRITICAL: Arbitrary origin reflection with credentials"
elif acao == evil_origin:
return "WARNING: Arbitrary origin reflection (no credentials)"
return None
The tool's intelligence lies in its context-aware severity assessment. It recognizes that most CORS misconfigurations are only exploitable when Access-Control-Allow-Credentials: true is present. Without this header, the browser won't send cookies or authentication tokens with the cross-origin request, making the misconfiguration largely harmless. CORStest flags findings with credentials support as high-severity, while marking credential-less issues as informational—a nuance that reduces false positive fatigue.
The multiprocessing design is crucial for performance at scale. When scanning the Alexa top 1 million domains, sequential testing would take weeks. By spawning 32 worker processes that each handle a chunk of the domain list, the researchers completed the scan in approximately 14 hours. Each worker maintains its own HTTP session, avoiding the GIL (Global Interpreter Lock) bottleneck that would plague a threading-based approach:
from multiprocessing import Pool
def scan_domain(domain):
results = []
test_origins = [
"null",
"https://evil.com",
f"https://evil.{domain}",
f"https://{domain}.evil.com",
"https://jsfiddle.net"
]
for origin in test_origins:
result = test_cors(domain, origin)
if result:
results.append(result)
return results
# Parallel execution across domain list
with Pool(processes=32) as pool:
all_results = pool.map(scan_domain, domain_list)
The tool tests eight specific misconfiguration categories based on Kettle's research: null origin allowance, pre-domain wildcards (evil-target.com), post-domain wildcards (target.com.evil.com), subdomain allowances, trust of third-party development sites, HTTPS-to-HTTP origin trust, arbitrary origin reflection, and wildcard with credentials (which browsers block, but CORStest checks anyway). Each category represents a pattern that researchers discovered on major production websites.
CORStest outputs results in a simple text format with severity indicators. When it finds a domain allowing attacker.com with credentials enabled, it flags it with [HIGH] or similar markers. This makes it easy to grep through results or pipe into other security tooling. The scan operates at the HTTP layer using the requests library, sending GET requests by default—sufficient for CORS policy detection since the preflight and response headers reveal the configuration regardless of the HTTP method used for discovery.
Gotcha
CORStest's biggest limitation is right in its README: the authors describe it as "quick & dirty." In practice, this means limited error handling and no retry logic. If a domain times out or returns a connection error, that domain gets skipped—there's no exponential backoff, no intelligent retry mechanism. For large-scale scanning, you'll lose data points to transient network issues. The tool also only tests the root path of each domain, completely missing CORS configurations on API endpoints like /api/v1/users or /graphql. Since modern applications often apply different CORS policies to different routes (stricter on auth endpoints, looser on public APIs), this is a significant blind spot.
The output format is another practical limitation. CORStest prints findings to stdout in a human-readable but unstructured format. There's no JSON export, no CSV option, no integration with vulnerability databases or security dashboards. If you're running this as part of a continuous security monitoring pipeline or feeding results into Jira/DefectDojo, you'll need to write parsing scripts. The tool also doesn't provide proof-of-concept exploit code or even detailed remediation guidance—it tells you there's a misconfiguration but leaves the "so what" and "now what" entirely to you. For bug bounty hunters or pentesters, this means extra work translating findings into demonstrable impact.
Verdict
Use if: You're conducting reconnaissance across a large number of domains (hundreds to thousands) and need to quickly identify CORS misconfigurations as potential entry points. It's perfect for the initial phase of bug bounty hunting, security audits of multi-tenant platforms, or research projects analyzing security trends across the web. The parallel processing and research-backed test cases make it significantly faster and more reliable than manual testing with curl scripts. Skip if: You need comprehensive API endpoint testing, detailed reporting for compliance purposes, or enterprise-grade tooling with support and maintenance. Also skip if you're testing a single application in depth—you'll get better results with Burp Suite's manual testing capabilities or a more full-featured scanner like OWASP ZAP. The tool's "quick & dirty" architecture means it's best suited for broad reconnaissance rather than thorough application security assessment.