The CORS Wildcard That Was Not Actually Safe
The origin header echoed back whatever the request sent and the credentials flag was on, so any site could read the authenticated response on the user's…
Between the protected and the echo sit 6 quiet assumptions, and the incident is the story of one of them failing.
Strip the reflected origin with credentials down and one property makes it a bypass rather than a misconfiguration: the wildcard alone is safe because the browser refuses to send credentials with it, so the code reflects the origin instead, and the reflection defeats the protection the wildcard's restriction provided.
The case was a JSON API with cookie sessions; the mechanics generalise to any endpoint that reflects the origin and allows credentials.
Why the wildcard looked safe
The CORS wildcard is the asterisk, and the asterisk is the any origin, and the any origin is the permissive, and the permissive is the unsafe, except that the browser refuses to send credentials when the origin is the asterisk, and the refuses is the protection, and the protection is the reason the wildcard with cookies does not work, and the not working is the developer's problem, and the problem is the fix, and the fix was the reflection.
The reflection is the echoed origin, and the echoed is the request's origin, and the request's origin is the attacker's site, and the attacker's site is the allowed, and the allowed is the credentials, and the credentials are the cookie, and the cookie is the session, and the session is the auth, and the auth is the read, and the read is the breach, and the breach is the reflection's cost.
The reflection was the workaround, and the workaround was the local development, and the local was the localhost, and the localhost was the origin, and the origin was the reflected, and the reflected was the working, and the working was the shipped, and the shipped was the production, and the production was the any origin, and the any origin was the breach, and the breach is the reason the workaround should not ship.
Why the browser allowed it
The browser's rule is the credentials require the specific origin, and the specific is the not asterisk, and the not asterisk is the reflection, and the reflection is the specific, and the specific is the allowed, and the allowed is the credentials, and the credentials are the sent, and the sent is the cookie, and the cookie is the auth, and the auth is the response, and the response is the read, and the read is the attacker's.
The attacker's page makes the request with the credentials flag, and the flag is the include, and the include is the cookie, and the cookie is the user's session, and the session is the identity, and the identity is the response, and the response is the data, and the data is the read, and the read is the cross origin, and the cross origin is the allowed by the reflection, and the allowed is the breach.
This is the same ride along as the session cookie without SameSite that travelled cross site, and the shared property is the one worth fixing.
The fix, in order
Allow an explicit list of origins. The server checks the origin against the list, and the list is the allowed, and the allowed is the specific, and the specific is the fix, and the fix is the enumeration, and the enumeration is the discipline, because the reflected origin is the any origin, and the any is the breach.
Never reflect the request's origin. The reflection is the bypass, and the bypass is the breach, and the breach is the fix's target, and the target is the removal, and the removal is the one line, and the line is the prevention, and the prevention is the rule, because the reflection is indistinguishable from the wildcard except that it defeats the credentials protection.
Separate the development configuration. The localhost origin is allowed in development only, and the only is the environment check, and the check is the guard, and the guard is the fix, and the fix is the configuration, and the configuration is the separation, and the separation is the discipline, because the workaround that ships is the breach.
Use SameSite on the session cookie. The SameSite is the browser's restriction, and the restriction is the cross site's block, and the block is the defence in depth, and the depth is the second layer, and the layer is the mitigation, and the mitigation is the fix, and the fix is the cookie's attribute, and the attribute is the one setting.
Prefer a header token over a cookie for cross-origin APIs. The header is the explicit, and the explicit is the not automatic, and the not automatic is the attacker's inability, and the inability is the fix, and the fix is the design, and the design is the discipline, because the cookie is sent by the browser and the header is sent by the code, and the code is the attacker's obstacle.
The takeaway
A CORS configuration that reflects the request's origin and allows credentials lets any site read the authenticated response with the user's cookie. Allow an explicit origin list, never reflect the origin, separate the development configuration, set SameSite on the session cookie, and prefer a header token for cross-origin APIs.
The API was behind a session cookie and the cookie was the protection, and the CORS handler echoed the request's origin so the credentials flag would work, and the echo made every origin allowed, so any page could read the user's data. The lesson I keep is the property, not the incident.
Published on debugly.dev by Rohit Bhadani | Founder of Krova Cloud