How to fix CORS misconfiguration?

cors-misconfiguration global

The issue

A loose CORS configuration lets other websites read responses meant only for your own pages. The serious case is an endpoint that accepts any origin and allows credentials: a page on another site can request it with the visitor’s cookies and read their data. Other cases expose responses that were never meant to be public, or let a cache hand one origin’s permission to another.

The cause is usually code or middleware that echoes the request’s Origin header back without checking it, allows the null origin, or varies the header by origin without telling caches.

Scope

CORS headers are set per response, but the logic is usually shared: one middleware, gateway or CDN rule covers every endpoint behind it. A mistake there applies to all of them.

How to check you have it

  • By hand: send a request with a foreign origin, curl -sI -H "Origin: https://attacker.example" https://your-site.example/api/…. If the response echoes that origin in Access-Control-Allow-Origin, especially with Access-Control-Allow-Credentials: true, it is open.
  • Null origin: repeat with -H "Origin: null". A response of Access-Control-Allow-Origin: null trusts sandboxed frames and local files.
  • Caching: where Access-Control-Allow-Origin changes with the request’s origin, the response should also carry Vary: Origin.

The fix

Check each request’s Origin against an exact list of trusted origins, and reflect it only if it is on the list.

On credentialed endpoints, send Access-Control-Allow-Credentials: true only alongside a listed origin. Use Access-Control-Allow-Origin: * only for responses that are public, unauthenticated and hold nothing internal. Remove null from any allowlist. Add Vary: Origin wherever the header depends on the request. Preflight OPTIONS responses need the same origin check.

Done when: only listed origins are reflected, credentials never go with an unchecked origin, null is refused, and the check above comes back clean.

Do not

  • Do not reflect the Origin header without checking it against a list.
  • Do not pair Access-Control-Allow-Credentials: true with a wildcard or an unchecked origin.
  • Do not put null in the list of allowed origins.
  • Do not leave out Vary: Origin when Access-Control-Allow-Origin depends on the request.

The four patterns

Echoed origin with credentials. Browsers refuse * together with credentials, but a server that echoes whatever origin asks has the same effect: any site can make a credentialed request and read the reply.

Open wildcard. Access-Control-Allow-Origin: * without credentials lets any site read the response. That is right for public data and wrong for anything internal.

Null origin. Sandboxed iframes, data: documents and local files send Origin: null. Allowing it trusts contexts anyone can create.

Missing Vary. Without Vary: Origin, a shared cache can store a response with one origin’s permission and serve it to another, breaking legitimate callers or admitting the wrong one.

FAQ

Why do browsers reject Access-Control-Allow-Origin: * with credentials but the vulnerability still exists?

Because vulnerable servers rarely send *. They echo the requesting origin, and the browser sees a specific match and allows the credentialed response.

Is Access-Control-Allow-Origin: * safe for public APIs?

Yes, when the endpoint needs no authentication, returns nothing internal and doesn’t allow credentials. Otherwise, check origins against a list.

How does an attacker exploit null origin?

They put a sandboxed iframe with scripts allowed on a page they control. Code in it has the origin null, so a server that allows null lets it read the response and pass it out of the frame.

What happens if Vary: Origin is missing and no CDN is in use?

Without a shared cache between client and server, nothing goes wrong. Caching layers tend to be added later without anyone revisiting CORS, so it is worth setting anyway.

Can CORS misconfiguration be exploited without user interaction?

Beyond visiting the attacker’s page, no interaction is needed. The request runs when that page loads.

Should origin validation use a regex or an exact-match list?

An exact-match list of full origins. Patterns are easy to get wrong: an unanchored example\.com$ also matches evil-example.com.

Which product detects this

Probeo checks this on every page of your site as part of Site Clarity. This page explains one instance; the product tells you every page that has it.