Stiamo esponendo un'API che i partner possono utilizzare solo sui domini che hanno registrato con noi. Il suo contenuto è in parte pubblico (ma preferibilmente da mostrare solo sui domini che conosciamo), ma è per lo più privato per i nostri utenti. Così:
Per determinare cosa viene mostrato, il nostro utente deve essere loggato con noi, ma questo viene gestito separatamente.
Per determinare dove vengono mostrati i dati, viene utilizzata una chiave API pubblica per limitare l'accesso ai domini che conosciamo e soprattutto per garantire che i dati degli utenti privati non siano vulnerabili a CSRF .
Questa chiave API è effettivamente visibile a chiunque, non autentichiamo il nostro partner in nessun altro modo e non abbiamo bisogno di REFERER . Tuttavia, è sicuro:
Quando get-csrf-token.js?apiKey=abc123
viene richiesto il nostro :
Cerca la chiave abc123
nel database e ottieni un elenco di domini validi per quella chiave.
Cerca il cookie di convalida CSRF. Se non esiste, genera un valore casuale sicuro e inseriscilo in un cookie di sessione solo HTTP . Se il cookie esisteva, ottieni il valore casuale esistente.
Crea un token CSRF dalla chiave API e il valore casuale dal cookie e firmalo . (Invece di mantenere un elenco di token sul server, stiamo firmando i valori. Entrambi i valori saranno leggibili nel token firmato, va bene.)
Imposta la risposta in modo che non venga memorizzata nella cache, aggiungi il cookie e restituisci uno script come:
var apiConfig = apiConfig || {};
if(document.domain === 'expected-domain.com'
|| document.domain === 'www.expected-domain.com') {
apiConfig.csrfToken = 'API key, random value, signature';
// Invoke a callback if the partner wants us to
if(typeof apiConfig.fnInit !== 'undefined') {
apiConfig.fnInit();
}
} else {
alert('This site is not authorised for this API key.');
}
Appunti:
Quanto sopra non impedisce a uno script lato server di simulare una richiesta, ma garantisce solo che il dominio corrisponda se richiesto da un browser.
La stessa politica di origine per JavaScript garantisce che un browser non possa utilizzare XHR (Ajax) per caricare e quindi ispezionare l'origine JavaScript. Invece, un normale browser può caricarlo solo utilizzando <script src="https://our-api.com/get-csrf-token.js?apiKey=abc123">
(o un equivalente dinamico) e quindi eseguirà il codice. Ovviamente, il tuo server non dovrebbe supportare la condivisione delle risorse tra le origini né JSONP per JavaScript generato.
Uno script del browser può modificare il valore di document.domain
prima di caricare lo script precedente. Ma la stessa politica di origine consente solo di accorciare il dominio rimuovendo i prefissi, come la riscrittura subdomain.example.com
in solo example.com
, o myblog.wordpress.com
in wordpress.com
, o in alcuni browser anche bbc.co.uk
in co.uk
.
Se il file JavaScript viene recuperato utilizzando uno script lato server, anche il server riceverà il cookie. Tuttavia, un server di terze parti non può fare in modo che il browser di un utente associ quel cookie al nostro dominio. Quindi, un token CSRF e un cookie di convalida che sono stati recuperati utilizzando uno script lato server, possono essere utilizzati solo dalle successive chiamate lato server, non in un browser. Tuttavia, tali chiamate lato server non includeranno mai il cookie dell'utente e quindi possono solo recuperare dati pubblici. Si tratta degli stessi dati che uno script lato server potrebbe raccogliere direttamente dal sito Web del partner.
Quando un utente accede, imposta alcuni cookie utente nel modo che preferisci. (L'utente potrebbe aver già effettuato l'accesso prima che JavaScript fosse richiesto.)
Tutte le successive richieste API al server (comprese le richieste GET e JSONP) devono includere il token CSRF, il cookie di convalida CSRF e (se connesso) il cookie dell'utente. Il server può ora determinare se la richiesta deve essere considerata attendibile:
La presenza di un token CSRF valido garantisce che JavaScript sia stato caricato dal dominio previsto, se caricato da un browser.
La presenza del token CSRF senza il cookie di convalida indica un falso.
La presenza sia del token CSRF che del cookie di convalida CSRF non garantisce nulla: potrebbe trattarsi di una richiesta lato server falsificata o di una richiesta valida da un browser. (Non potrebbe essere una richiesta da un browser effettuata da un dominio non supportato.)
La presenza del cookie utente garantisce che l'utente sia connesso, ma non garantisce che l'utente sia un membro del partner specificato, né che l'utente stia visualizzando il sito Web corretto.
La presenza del cookie dell'utente senza il cookie di convalida CSRF indica un falso.
La presenza del cookie dell'utente garantisce che la richiesta corrente venga effettuata tramite un browser. (Supponendo che un utente non inserisca le proprie credenziali su un sito Web sconosciuto e supponendo che non ci interessi che gli utenti utilizzino le proprie credenziali per effettuare alcune richieste lato server.) Se abbiamo anche il cookie di convalida CSRF, allora quel cookie di convalida CSRF era ricevuto anche utilizzando un browser. Successivamente, se abbiamo anche un token CSRF con una firma valida, eil numero casuale nel cookie di convalida CSRF corrisponde a quello in quel token CSRF, quindi anche il JavaScript per quel token è stato ricevuto durante quella stessa richiesta precedente durante la quale è stato impostato il cookie CSRF, quindi anche utilizzando un browser. Ciò quindi implica anche che il codice JavaScript sopra è stato eseguito prima che il token fosse impostato e che in quel momento il dominio era valido per la chiave API fornita.
Quindi: il server ora può utilizzare in sicurezza la chiave API dal token firmato.
Se in qualsiasi momento il server non si fida della richiesta, viene restituito un 403 Forbidden. Il widget può rispondere a ciò mostrando un avviso all'utente.
Non è necessario firmare il cookie di convalida CSRF, poiché lo stiamo confrontando con il token CSRF firmato. La mancata firma del cookie rende ogni richiesta HTTP più breve e la convalida del server un po 'più veloce.
Il token CSRF generato è valido a tempo indeterminato, ma solo in combinazione con il cookie di convalida, quindi in modo efficace fino alla chiusura del browser.
Potremmo limitare la durata della firma del token. Potremmo eliminare il cookie di convalida CSRF quando l'utente si disconnette, per soddisfare la raccomandazione OWASP . E per non condividere il numero casuale per utente tra più partner, è possibile aggiungere la chiave API al nome del cookie. Ma anche in questo caso non è possibile aggiornare facilmente il cookie di convalida CSRF quando viene richiesto un nuovo token, poiché gli utenti potrebbero navigare nello stesso sito in più finestre, condividendo un singolo cookie (che, durante l'aggiornamento, verrebbe aggiornato in tutte le finestre, dopodiché il Il token JavaScript nelle altre finestre non corrisponderebbe più a quel singolo cookie).
Per coloro che utilizzano OAuth, vedere anche OAuth e widget lato client , da cui ho preso l'idea JavaScript. Per l' utilizzo lato server dell'API, in cui non possiamo fare affidamento sul codice JavaScript per limitare il dominio, stiamo usando chiavi segrete invece delle chiavi API pubbliche.