PenTesting Company
← Blog

The Dangers of Misconfiguring CORS Headers on a Web API Server

8 min read

Cross-Origin Resource Sharing (CORS) is how a server tells a browser which other origins may read its responses. It is a browser-enforced relaxation of the same-origin policy, not an access control on the server, so it does nothing to stop requests from tools that are not browsers. The main risk is a policy that reflects any origin back while also allowing credentials, which can let another site read authenticated responses in a signed-in user's browser.

Safer and riskier CORS setups

The two headers that matter most are Access-Control-Allow-Origin and Access-Control-Allow-Credentials. This table compares common setups.

Safer and riskier Access-Control-Allow-Origin and Access-Control-Allow-Credentials setups
SetupRiskWhy
Access-Control-Allow-Origin: * without credentialsGenerally acceptable for public dataFine for truly public, read-only data. Browsers send these requests without credentials, so cookies and other credentials are not included.
Access-Control-Allow-Origin: * with Access-Control-Allow-Credentials: trueDoes not workBrowsers reject this combination and do not share the response. Teams who hit this error sometimes switch to reflecting the origin, which is riskier.
Reflecting any Origin header back, with Access-Control-Allow-Credentials: trueHighAny site can read authenticated responses in a signed-in user's browser.
Allowing the null originRiskySandboxed iframes and some file contexts send Origin: null, so allowing it can let untrusted content read responses.
Loose suffix or prefix matchingRiskyA check that accepts any origin ending in example.test also accepts https://attackerexample.test. A prefix check on https://example.test also accepts https://example.test.evil.example.
Explicit allowlist with exact match, plus Vary: OriginSafer patternOnly listed origins get the header, and Vary: Origin keeps caches from serving one origin's response to another.

CORS is not a CSRF defense

A permissive CORS policy does not protect you from Cross-Site Request Forgery, and a strict one is not a substitute for anti-CSRF tokens. Treat them as separate controls. Anti-CSRF tokens remain your first line of defense against forged requests, and CORS controls who can read responses.

Configure it safely

Prefer an explicit allowlist of the exact origins that need access, and avoid pairing wildcards with credentials. A safer response looks like this:

Access-Control-Allow-Origin: https://app.example.com
Access-Control-Allow-Credentials: true
Vary: Origin

Set the origin per request from a known allowlist rather than echoing it blindly, and include Vary: Origin so caches do not serve one origin's response to another.

How to check your own API

You can run a quick self-check against an API you own. Send a request with an origin you do not trust and read the response headers:

curl -s -o /dev/null -D - -H "Origin: https://evil.example" https://api.example.com/endpoint

This sends a normal GET request and prints only the response headers. It avoids -I, which sends a HEAD request, because some APIs route HEAD differently from GET and can return different headers.

Look for Access-Control-Allow-Origin and Access-Control-Allow-Credentials in the response. If the untrusted origin comes back with credentials allowed, the policy is reflecting origins.

Illustrative risky response:

HTTP/1.1 200 OK
Content-Type: application/json
Access-Control-Allow-Origin: https://evil.example
Access-Control-Allow-Credentials: true

Illustrative safer response, with no Access-Control-Allow-Origin header for the unknown origin:

HTTP/1.1 200 OK
Content-Type: application/json
Vary: Origin

Repeat the check with -H "Origin: null", and with lookalike hostnames such as https://attackerexample.test for an API on example.test, to catch null-origin and loose matching rules. Check each endpoint that returns sensitive data, since policies are sometimes set per route.

Fix checklist

  • Use an explicit allowlist of exact origins, including scheme and port, and compare origins exactly.
  • Return Access-Control-Allow-Origin only for origins on the allowlist, and send no CORS headers for anything else.
  • Send Access-Control-Allow-Credentials: true only where cross-origin credentialed requests are actually needed.
  • Do not allow the null origin.
  • Use * only for public, read-only data that needs no credentials.
  • Add Vary: Origin whenever the header value changes by origin.
  • Re-run the self-check after each change, and keep authentication and anti-CSRF controls in place, since CORS is not an access control.

A tight CORS policy reduces risk, but it is one layer among several. A misconfigured policy is a common finding in a web application security test. If you would like us to review your API's CORS policy along with the rest of the application, we can include it in a scoped web application test against a scope we agree together. Contact us to get started.

Have a system you want tested?

Tell us what you are working with and we will follow up with next steps.