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 inAccess-Control-Allow-Origin, especially withAccess-Control-Allow-Credentials: true, it is open. - Null origin: repeat with
-H "Origin: null". A response ofAccess-Control-Allow-Origin: nulltrusts sandboxed frames and local files. - Caching: where
Access-Control-Allow-Originchanges with the request’s origin, the response should also carryVary: 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
Originheader without checking it against a list. - Do not pair
Access-Control-Allow-Credentials: truewith a wildcard or an unchecked origin. - Do not put
nullin the list of allowed origins. - Do not leave out
Vary: OriginwhenAccess-Control-Allow-Origindepends 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.