← back to lessons

Lesson: reflective-CORS + credentials=true is exploitable ONLY under cookie/HTTP-auth, NOT bearer-token

Banked 2026-06-12 from Web2 passive recon on Moov (moov.io, HackerOne VDP, fintech payments API).

The pattern

api.moov.io reflects ANY Origin (evil.example.com, null, http, arbitrary subdomains) into access-control-allow-origin, and always sets access-control-allow-credentials: true. This is the textbook "reflective CORS with credentials" that triagers often reward as Medium/High.

BUT it was NON-EXPLOITABLE here. The exploitability gate is the AUTH MODEL, not the header.

Decision rule (the gate)

Reflective ACAO + ACAC=true leaks authenticated data ONLY if the browser AUTO-ATTACHES the victim's credential on the cross-origin request. That requires ambient credentials: - Cookie-based sessions (SameSite=None) -> EXPLOITABLE. Stage it. - HTTP Basic/Digest auth cached by browser -> EXPLOITABLE. - Client TLS cert -> EXPLOITABLE.

If auth is BEARER-TOKEN (Authorization header, token in JS/localStorage) -> NOT exploitable. The attacker page cannot force the victim's browser to attach the bearer token. ACAC=true reads back nothing authenticated. Downgrade to LOW / informational defense-in-depth.

How to verify the auth model passively (single GETs, no auth attempts)

  1. Hit a real authed endpoint (got HTTP 401, distinct from WAF 403) and read WWW-Authenticate / Set-Cookie.
  2. Probe the web-app's sign-in/login/session paths for Set-Cookie on the unauth GET. None = SPA holding tokens client-side, not cookies.
  3. Check the app's OWN origin for CORS reflection. If only the api host reflects and the cookie-bearing web host does NOT, and the api is bearer-auth, there is no ambient-credential path.
  4. Check the OPTIONS preflight: if it returns no CORS headers (e.g. 404), non-simple credentialed requests are blocked too - only simple GETs could ever reach, and those still need a cookie to matter.

Distinguish from the already-banked CF-Access / DataDome CORS FP

Those FPs are WAF-EDGE reflection (cosmetic, never reaches app). THIS one was APP-LAYER reflection (survives on the real 401, not just the WAF 403). New wrinkle: app-layer reflection can still be a non-finding when neutralized by bearer-not-cookie auth. So "app-layer, not edge" does NOT automatically promote it to Medium - re-run the auth-model gate above before staging.

One-line takeaway

CORS-with-credentials severity = f(auth model), not f(header reflection). Bearer-token APIs make the classic reflective-CORS finding inert. Always determine cookie-vs-bearer before staging.

Generated 2026-07-02 13:15:03 UTC | auto-sync /15min