Every production API has a CORS policy, whether the team intended one or not. The browser enforces whatever the server declares. A server that declares the wrong thing does not get bypassed, it gets obeyed instead.
This blog post documents five CORS misconfiguration patterns and the automated checks used to detect them. Each pattern is a distinct server-side decision that, under the right conditions, allows an attacker to read authenticated API responses from a page they control. The companion diagram file shows the full attack execution chain for the most impactful variant.
When a browser script on page-a.com makes a request to api-b.com, the browser adds an Origin header to the request. The server at api-b.com responds with Access-Control-Allow-Origin. If that header matches the requesting origin, the browser allows the script to read the response. If it does not match, the browser suppresses the response body regardless of the HTTP status code.
The critical design point: the browser enforces what the server declares. There is no override. A server that tells the browser to trust https://attacker.com will be trusted. The question is purely whether the server is making that declaration correctly.
Where it fails is on the server side, in the origin validation logic. Developers commonly choose between four approaches: return a static value, reflect whatever origin the client sent, check against a regex, or return a wildcard. Each of these can be implemented correctly or incorrectly. The four failure modes map almost exactly to the five patterns below.
Classification: CWE-942, OWASP API Security Top 10 A8, CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:L/I:L/A:N.
The server returns Access-Control-Allow-Origin: *. Any origin can read the response.
For public, unauthenticated APIs, this is often intentional and acceptable. For anything behind a session, it is not. The wildcard cannot be paired with Access-Control-Allow-Credentials: true — browsers block that combination. So the attack surface here is limited to endpoints where the data is sensitive but not gated by cookie auth. Response body tokens, API keys embedded in responses, and user enumeration endpoints all fall into this category.
The server reads the Origin request header and echoes it back verbatim in Access-Control-Allow-Origin. Pair that with Access-Control-Allow-Credentials: true, and the endpoint will trust any origin the attacker claims.
This is the highest-severity pattern in the set. The browser receives a credentialed response with an ACAO header that names the requesting origin specifically. The browser allows the script to read it. The victim’s cookies were sent because cookie attachment is governed by the request target’s origin, not the page that initiated the request. The attacker gets the response body with full session context.
The reason this appears so often in production: developers need to support multiple frontend origins (staging, prod, multiple product domains) and find dynamic reflection simpler than maintaining an allowlist. The intent is multi-origin support. The implementation is open trust.
Origin: null is sent by browsers in specific contexts — sandboxed iframes, file:// pages, data: URIs. If the server responds to a null origin with Access-Control-Allow-Origin: null and Access-Control-Allow-Credentials: true, any attacker-controlled sandboxed iframe on any domain can read the response.
This pattern usually traces to a developer who tested the API locally via a file:// page. The browser sends null for local files. The developer added null to the trusted origins to make local testing work. It went to production.
Access-Control-Allow-Credentials: true in isolation means nothing. The problem is when it appears alongside Access-Control-Allow-Origin: * or Access-Control-Allow-Origin: null. Browsers refuse to act on the * + credentials combination, so active exploitation via a browser is blocked. The misconfiguration still warrants a finding: non-browser clients, mobile apps, and custom HTTP tooling do not enforce the same rules, and the server’s stated intent is wrong regardless of whether a compliant browser would honour it.
Origin validation via regex is the most common approach on APIs that serve multiple legitimate domains. It is also where subtle errors cause the most damage, because the server appears to be doing careful validation when it is not.
The typical flaw: the pattern is anchored at the start but not the end, or uses an unescaped . that matches any character, or permits http:// alongside https://. Consider:
Against this pattern, https://newexample.com matches. So does http://evil-example.com, https://notexample.com, and https://notexample.com.attacker.io. An attacker who controls any domain with example at the end of the name can bypass the check.
A correct regex for example.com looks like this:
The difference between the two patterns is five characters. The consequence of getting it wrong is that the attacker registers a domain matching the loose pattern and treats the entire API as if it trusted them explicitly.
Each misconfiguration has a natural home in a different route:
Routes 2 and 5 are the most dangerous. Both combine origin trust with credential access. Route 5 is worse from a triage perspective because it does not obviously look broken. The developer wrote validation logic. It checks something. The flaw requires testing the pattern against adversarial inputs rather than reading the code.
The detection functions covered above come together in a single CORSScanner class. Each method applies one check — wildcard, reflected origin, null origin, broad credentials, and regex bypass — to the target URL, returning a consistent result.
The scanner does not read response bodies. Header-only detection keeps it fast and avoids the need to parse heterogeneous response formats. Whether the data in the response body is sensitive is a reporting concern, not a detection concern.
Reflected origin is the most common finding at scale. The gap between “I need to support multiple origins” and “the simplest way to do this is to echo back whatever I receive” is small enough that developers cross it frequently without recognising the security implications.
Regex bypass findings tend to cluster around services that underwent domain migration. A developer adds a new product subdomain and, rather than extending the allowlist, loosens the pattern. The original set of legitimate origins stays safe. The new pattern silently admits adjacent domains.
Null origin trust appears less often but is more operationally predictable when it does. Almost every instance traces to a local development configuration that was not stripped before deployment. The fix is always the same: remove null from the trusted set, and use a reverse proxy or local mock that sends a real origin during development.
Credentials-with-wildcard is noteworthy precisely because browsers block it. The misconfiguration is not exploitable via a compliant browser. Its presence still indicates that the team did not model the CORS policy intentionally — they combined flags without understanding the constraint, and got lucky that browsers enforce it. Non-browser HTTP clients, native mobile apps using system HTTP libraries, and some older WebView implementations may not apply the same restriction.
For reflected-origin and regex-bypass misconfigurations on authenticated endpoints, the attacker’s exploit is brief:
The victim’s browser sends their session cookie to api.target.com because cookies follow the request target, not the origin of the initiating script. The server returns a response with an ACAO header trusting the attacker’s origin. The browser reads credentials: “include” and the matching ACAO, grants the script access to the response body, and the data is shipped to the collection endpoint.
One page visit. No user interaction beyond navigation. No XSS required. No prior credential theft.
The impact ceiling is whatever the affected endpoints expose. An API that returns PII on the profile endpoint, session metadata on the auth endpoint, and internal configuration somewhere on the management surface gives an attacker read access to all of it in a single automated pass. Mutation is possible via the same mechanism on POST endpoints if the server does not separately validate method-level CORS constraints.
Maintain an explicit allowlist as the primary origin gate. The list should live in application configuration, not in route handlers, so it can be audited and updated independently.
Use set membership for the initial check, regex only as a secondary format validator. The set check is fast and eliminates adversarial values before the regex runs. The regex rejects any value that looks structurally suspicious even if it matches a known string.
Add Vary: Origin to every response where ACAO is set dynamically. This is a caching correctness requirement.
Limit Access-Control-Allow-Credentials: true to endpoints that require session-based authentication. Public data endpoints do not need it.
Run the scanner against every endpoint in the API surface, not just endpoints that obviously handle sensitive data. CORS misconfigurations on low-sensitivity endpoints can still leak authentication context that enables further access.
Extend scanner probe sets periodically. The five regex bypass origins in this implementation cover the most common patterns but not all of them. Subdomain takeover against a subdomain-trusting regex is a variant that requires DNS-level visibility to cover fully.
CORS misconfigurations are a class of vulnerability where the exploit requires almost nothing from the attacker and almost everything from the browser. The browser faithfully implements whatever trust model the server declares. When the server declares the wrong trust model, the browser becomes the delivery mechanism.
Of the five patterns here, reflected origin with credentials is the one to prioritise in triage. It is the most common, the most impactful, and the easiest to confirm with a single crafted request. A server that reflects any origin with credentials enabled is, for practical purposes, unauthenticated to any attacker who can get a victim to load a URL.
The fix is a static allowlist. Every other approach — dynamic reflection, regex matching without strict anchoring, null origin exceptions — creates a category of input the server has not fully considered. Only an explicit list of approved origins makes the trust boundary visible enough to audit.

Comments
Nothing yet. Say the first thing.
Sign in to join the conversation.