Se hai mai visto una console del browser con un errore come:
Access to fetch at 'https://api.example.com'
from origin 'https://app.example.com'
has been blocked by CORS policy
probabilmente ti sei imbattuto nel CORS.
Il Cross-Origin Resource Sharing è uno dei meccanismi fondamentali con cui i browser controllano le richieste effettuate tra origini differenti.
È anche uno degli argomenti che genera più confusione quando si parla di sicurezza web.
Cos'è un'origine?
Prima di capire CORS bisogna capire cosa significa "origin".
Un'origine è determinata principalmente da:
schema + host + porta
Per esempio:
https://example.com
https://api.example.com
https://example.com:8443
possono rappresentare origini differenti.
Quindi un'applicazione web caricata da:
https://app.example.com
che vuole effettuare una richiesta verso:
https://api.example.com
sta effettuando una richiesta cross-origin.
A cosa serve CORS?
Il browser applica una serie di restrizioni alle richieste cross-origin.
Il server può però comunicare al browser quali origini sono autorizzate ad accedere alle proprie risorse attraverso specifici HTTP response headers.
Il più conosciuto è:
Access-Control-Allow-Origin: https://app.example.com
In questo caso il server sta indicando che l'origine specificata può accedere alla risorsa secondo le regole CORS.
Un esempio pratico
Immaginiamo di avere:
Frontend:
https://app.example.com
API:
https://api.example.com
Il frontend potrebbe effettuare:
fetch("https://api.example.com/users");
Il browser controlla la risposta dell'API.
Se il server restituisce:
Access-Control-Allow-Origin: https://app.example.com
la risposta può essere resa disponibile al codice JavaScript dell'origine autorizzata.
Se invece il server non autorizza l'origine, il browser può impedire al codice della pagina di leggere la risposta.
CORS non è un firewall
Questo è uno dei concetti più importanti.
CORS non impedisce necessariamente che una richiesta raggiunga il server.
È principalmente un meccanismo del browser che controlla se una pagina web può leggere la risposta di una richiesta cross-origin.
Per questo non bisogna considerarlo un sostituto di:
- autenticazione;
- autorizzazione;
- firewall;
- controllo degli accessi;
- validazione degli input.
Un'API deve continuare a verificare autonomamente chi può accedere a una risorsa.
E il famoso *?
Una configurazione molto comune è:
Access-Control-Allow-Origin: *
Significa che la risorsa può essere resa accessibile da qualsiasi origine, secondo le altre regole applicabili.
Questo può essere perfettamente legittimo per alcune risorse pubbliche.
Per esempio, un'API che restituisce dati pubblici potrebbe non avere motivo di limitare l'origine.
Il problema nasce quando una policy molto permissiva viene utilizzata su risorse che dovrebbero essere accessibili soltanto a determinati contesti.
CORS e credenziali
La situazione diventa più delicata quando entrano in gioco credenziali, cookie o altre informazioni associate all'utente.
In questi casi bisogna valutare attentamente configurazioni come:
Access-Control-Allow-Credentials: true
insieme alla policy relativa alle origini autorizzate.
Una configurazione CORS permissiva non dovrebbe essere giudicata isolatamente: bisogna capire quali dati espone l'endpoint e quali meccanismi di autenticazione utilizza.
Preflight e richieste OPTIONS
Alcune richieste cross-origin provocano una richiesta preliminare chiamata preflight.
Il browser può inviare:
OPTIONS /api/users HTTP/1.1
Origin: https://app.example.com
Access-Control-Request-Method: DELETE
Il server può rispondere indicando quali operazioni sono consentite:
Access-Control-Allow-Origin: https://app.example.com
Access-Control-Allow-Methods: GET, POST, DELETE
Questo meccanismo permette al browser di verificare in anticipo se la richiesta è consentita.
Una configurazione CORS sbagliata è sempre una vulnerabilità?
No.
Anche questo è un punto importante per chi utilizza strumenti di security scanning.
Un server potrebbe avere:
Access-Control-Allow-Origin: *
e non esporre alcun dato sensibile.
In quel caso la configurazione potrebbe essere semplicemente molto permissiva, ma il rischio concreto dipende dalla risorsa.
Al contrario, una configurazione apparentemente più limitata potrebbe comunque essere problematica se applicata a endpoint contenenti dati sensibili e accompagnata da altre condizioni.
La valutazione deve quindi considerare:
- origine autorizzata;
- endpoint;
- dati restituiti;
- autenticazione;
- credenziali;
- metodi HTTP;
- comportamento del browser.
Cosa controllare
Quando analizzi CORS, puoi verificare:
- quali origini vengono autorizzate;
- se viene utilizzato
*; - se vengono abilitate credenziali;
- quali metodi HTTP sono consentiti;
- quali header possono essere inviati;
- quali endpoint utilizzano la policy;
- se le API espongono dati pubblici o riservati;
- se la configurazione cambia tra ambiente di produzione e staging.
CORS visto dall'esterno
Un controllo esterno può osservare le intestazioni restituite dal server.
Ad esempio:
curl -I \
-H "Origin: https://example.org" \
https://api.example.com/
La risposta può fornire indicazioni sulla configurazione CORS.
Naturalmente, un singolo endpoint non rappresenta necessariamente tutta l'API.
Potrebbero esserci policy differenti per:
/api/
/api/v1/
/api/v2/
/admin/
/public/
CORS e MiHakero
CORS è un ottimo esempio di perché un controllo di sicurezza non dovrebbe limitarsi a cercare stringhe "giuste" o "sbagliate".
Una configurazione deve essere interpretata in relazione a ciò che l'applicazione espone realmente.
MiHakero può aiutare a individuare configurazioni che meritano una verifica, lasciando poi all'analisi del contesto la valutazione dell'effettivo impatto.
Perché una configurazione permissiva non è automaticamente una vulnerabilità.
Il punto è capire chi può accedere a cosa e in quali condizioni.