How to check for CORS misconfiguration on your own site

To run a CORS misconfiguration check, log in to your app, send a request to a sensitive endpoint with an Origin header set to a domain you do not own, and read two response headers: Access-Control-Allow-Origin and Access-Control-Allow-Credentials. If the server echoes your made-up origin back and allows credentials, any site can read that response on behalf of a logged-in user[1]. The rest of this guide shows how to read each result, rate it, report it and fix it.
What a CORS misconfiguration is
A CORS misconfiguration is a cross-origin policy that trusts origins it should not. CORS is the HTTP-header mechanism a server uses to tell the browser which other origins may read its responses. When that policy is too loose, a foreign site that your user happens to visit can ask the browser to fetch data from your app and hand it over.
The weakness has a formal name: CWE-942, "Permissive Cross-domain Security Policy with Untrusted Domains"[3]. MITRE describes it as a product that uses a cross-domain policy, but the policy includes untrusted domains the web client is allowed to communicate with[3].
CORS policy is one of the settings worth checking by hand on a site built that way. The check takes a few minutes and needs no special tooling.
How to check for it by hand
Send an authenticated request with a foreign Origin header and read what the server answers. You can do this with curl, an HTTP client or an intercepting proxy, because the client decides what goes into the Origin header[4]. Only test sites and apps you own or are authorized to test.
- Log in to the app in a browser and pick an endpoint that returns sensitive data for the logged-in user, such as a profile or account API.
- Copy that request, including its session cookie or token, into your tool.
- Add an Origin header with a domain you do not own, for example https://evil.example, and send it.
- Read Access-Control-Allow-Origin and Access-Control-Allow-Credentials in the response.
- Repeat with the Origin header set to null.
- Repeat with a lookalike of your own domain, such as one that starts with your domain name and ends in a domain someone else could register.
Compare each result with the table below.
| Origin sent | Access-Control-Allow-Origin returned | Access-Control-Allow-Credentials | What it means |
|---|---|---|---|
| https://evil.example | https://evil.example | true | Reflected origin with credentials. The serious case. |
| null | null | true | Null origin trusted. A finding. |
| Lookalike of your domain | The lookalike, echoed back | true | Weak allowlist matching. A finding. |
| https://evil.example | * | Absent | Weak signal. Matters only if the data should not be public. |
| https://evil.example | Absent, or your own origin | Any | The foreign origin was not accepted. No finding. |
Which responses are dangerous and which are not
The dangerous responses are the ones that pair an origin you invented with permission to send credentials. PortSwigger puts the reflected case plainly: when an application reflects arbitrary origins in Access-Control-Allow-Origin, "absolutely any domain can access resources from the vulnerable domain"[1]. With credentials allowed, those resources include whatever the logged-in user can see.
Two other patterns reach the same result by a different route:
- Trusted null origin. Some applications allowlist the null origin to support local development, and the entry stays in place after launch[1].
- Weak allowlist matching. Mistakes often arise when implementing origin allowlists[1]. A check that asks whether the origin starts with, ends with or loosely matches your domain will also accept a domain an attacker registers to fit the pattern.
A bare wildcard is a weaker signal. When a request carries credentials and the response uses the wildcard, the browser blocks access to the response and reports a CORS error in the devtools console[2]. So a wildcard on its own does not hand over logged-in data. It deserves a closer look only when the endpoint returns something that should not be public in the first place.
One more point shapes how you read every result. Because a client can put any value in the Origin header to make the application return the resource[4], an accepted Origin value is not proof that the caller is your own front end.
How severe it is and what to report
Severity depends on what an attacker can read, not on the header itself. A reflected origin with credentials on an endpoint that returns personal or account data sits at the high end. The same policy on an endpoint that serves public data with no credentials sits at the low end. A CORS finding is not automatically high or critical, and we do not give a CVSS number here because the right score depends on the data exposed in your app.
A report that someone can act on ties the rating to a reproduction. Include:
- The exact request: method, URL and the session it was sent with.
- The Origin value you sent.
- The Access-Control-Allow-Origin and Access-Control-Allow-Credentials values returned.
- The data in the response that a foreign site could read.
- The classification, CWE-942[3].
- The rating, with one sentence on why that data justifies it.
For a model of the format, our sample report shows full output from a live application: discovered endpoints, vulnerability findings, severity ratings and reproduction steps, with URLs redacted. Every BattleTester report includes reproduction steps for each issue, with findings rated by severity.
How to fix a CORS misconfiguration
Fix it by replacing any logic that echoes the request's origin with a short allowlist of exact origins. The server should return an origin in Access-Control-Allow-Origin only when the incoming value matches one entry on that list, character for character.
- Compare whole strings. Drop prefix, suffix and loose regex checks, which are where allowlist mistakes come from[1].
- Remove null from the allowlist. If local development needs it, keep it in the development configuration only[1].
- Do not pair the wildcard with credentials. The browser blocks that response anyway[2], so a credentialed endpoint needs one specific allowed origin.
- Keep the wildcard for public data only. If an endpoint returns anything tied to a user, give it the allowlist.
- Do not treat Origin as authentication. OWASP states it is not recommended to use the Origin header to authenticate requests as coming from your site[4]. Require a valid session or token on the resource itself.
After the change, run the manual check again with all three Origin values. Each should now come back without your test origin in Access-Control-Allow-Origin.
What an automated scan covers and misses
An automated scan covers a different part of the problem, so use it next to the manual check, not in place of it. BattleTester's listed tests are broken access control, XSS, JWT security and business logic. A CORS test is not on that list, so the header check above stays a manual job.
What a scan adds is breadth. A BattleTester scan logs in and explores your site like a real user, and crawls the entire application as each role: anonymous, logged-in and admin. That matters for CORS work too, because the flaws a loose policy exposes are only as bad as the data behind each endpoint.
Know the limits before you rely on the result:
- With no login, the scanner tests only publicly accessible endpoints, and access control and privilege checks are limited or skipped.
- Without your credentials, it registers test accounts and uses them to crawl and test. This may miss important tests, and it cannot test privilege escalation or admin-only endpoints.
- It is not a comprehensive security audit and does not replace an assessment by qualified security professionals. If your app handles sensitive data, bring one in.
A free surface scan is a reasonable starting point, and it needs no credit card upfront. A full pentest costs $15 and is charged only if the scan finds High or Critical vulnerabilities.
FAQ
What does a CORS misconfiguration look like in the response headers?
Look for Access-Control-Allow-Origin carrying an origin you made up for the test, next to Access-Control-Allow-Credentials: true. A correctly configured server leaves your test origin out of the response.
Is a wildcard Access-Control-Allow-Origin always a vulnerability?
No. It is normal on endpoints that serve public data. Treat it as a finding only when the endpoint returns data that should not be readable by everyone without logging in.
What is the null origin issue in CORS?
It is an allowlist that accepts the literal value null as an origin. Test for it by sending Origin: null with a logged-in request and checking whether the server answers with null and allows credentials.
How severe is a CORS misconfiguration?
There is no single rating. Judge it by the most sensitive response a foreign origin can read with the user's credentials, and state that data in the report so the rating can be checked.
Sources
- Cross-origin resource sharing (CORS) - PortSwigger Web Security Academy, PortSwigger, accessed October 4, 2026
- Cross-Origin Resource Sharing (CORS) - HTTP | MDN, MDN Web Docs (Mozilla), accessed October 4, 2026
- CWE-942: Permissive Cross-domain Security Policy with Untrusted Domains, MITRE (CWE), accessed October 4, 2026
- CORS Origin Header Scrutiny - OWASP Community, OWASP Foundation, accessed October 4, 2026