Introduzione al Cross-Site Request Forgery (CSRF)
Nel panorama della sicurezza web, una delle vulnerabilità più insidiose e comuni è il Cross-Site Request Forgery, noto anche come CSRF o "web request forgery". A differenza di altri attacchi che mirano a rubare dati dal server o a iniettare codice malevolo (come l'SQL Injection o l'XSS), il CSRF non punta a sottrarre informazioni, ma a indurre l'utente a compiere azioni non desiderate su un sito in cui è già autenticato.
Immaginate uno scenario in cui un utente è loggato nel proprio account bancario. Mentre naviga in un'altra scheda del browser, visita un sito malevolo che contiene un link nascosto o uno script che invia una richiesta di trasferimento fondi verso un conto terzo. Poiché il browser invia automaticamente i cookie di sessione insieme alla richiesta, il server della banca riconosce l'utente come autenticato e processa l'operazione. L'utente non ha interagito consapevolmente con la banca, ma il server ha ricevuto una richiesta valida e autenticata.
In questo articolo esploreremo in profondità come funzionano questi attacchi e, soprattutto, come i CSRF Token rappresentino la difesa standard e più efficace per prevenire questo rischio.
Come funziona tecnicamente un attacco CSRF
Per comprendere la soluzione, dobbiamo prima capire il meccanismo di vulnerabilità. Il CSRF sfrutta due pilastri del funzionamento del web:
- La gestione automatica dei cookie: I browser sono progettati per inviare tutti i cookie associati a un dominio ogni volta che viene effettuata una richiesta verso quel dominio, indipendentemente dall'origine della richiesta (che sia un link interno o un form su un sito esterno).
- La natura stateless di HTTP: Il server si affida ai cookie (o ai token JWT) per identificare l'utente. Se il cookie è presente e valido, il server assume che l'azione sia stata intenzionalmente richiesta dall'utente.
Scenario di attacco passo dopo passo
Consideriamo un'applicazione web che permette di cambiare l'email dell'account tramite una richiesta POST a /update-email con il parametro email=nuovo@email.com.
- L'utente effettua l'accesso a
social-network.come riceve un cookie di sessione. - L'utente, senza fare logout, visita
sito-malevolo.net. - Il sito malevolo contiene un elemento HTML invisibile, come un form che viene inviato automaticamente tramite JavaScript:
<!-- Esempio di form malevolo su sito-malevolo.net -->
<form id="csrf-form" action="https://social-network.com/update-email" method="POST">
<input type="hidden" name="email" value="hacker@evil.com">
</form>
<script>
document.getElementById('csrf-form').submit();
</script>
- Il browser invia la richiesta a
social-network.comincludendo i cookie di sessione dell'utente. - Il server di
social-network.comverifica il cookie, vede che l'utente è autenticato e aggiorna l'email dell'account con quella dell'attaccante.
La soluzione: Il CSRF Token
Il CSRF Token (Cross-Site Request Forgery Token) è un valore univoco, segreto e imprevedibile generato dal server per ogni sessione utente o per ogni singola richiesta. L'obiettivo è aggiungere un elemento alla richiesta che l'attaccante non può conoscere né prevedere.
Perché il token funziona?
Mentre i cookie vengono inviati automaticamente dal browser, un token CSRF deve essere inserito esplicitamente nel corpo della richiesta (ad esempio in un campo hidden di un form o in un header HTTP). Poiché l'attaccante non ha accesso ai cookie (per via della Same-Origin Policy), non può leggere il valore del token generato dal server e non può quindi includerlo nella richiesta contraffatta.
Il server, al momento della ricezione della richiesta, confronta il token ricevuto con quello salvato nella sessione dell'utente. Se i due valori non coincidono o se il token è assente, la richiesta viene rifiutata con un errore (solitamente un HTTP 403 Forbidden).
Implementazione Pratica
Vediamo come implementare questo sistema in due contesti diversi: un'applicazione server-side tradizionale (come PHP/Laravel) e un'applicazione moderna basata su API (Node.js/React).
Esempio 1: Implementazione in PHP/Laravel
Laravel gestisce i CSRF token in modo quasi automatico. Quando si utilizza il middleware VerifyCsrfToken, Laravel genera un token e lo salva nella sessione. Lo sviluppatore deve solo includerlo nei form.
<!-- Nel file Blade di Laravel -->
<form method="POST" action="/profile/update">
<!-- La direttiva @csrf genera un campo hidden con il token -->
@csrf
<input type="text" name="username">
<button type="submit">Aggiorna</button>
</form>
Dietro le quinte, @csrf produce un HTML simile a questo:
<input type="hidden" name="_token" value="a7b8c9d0e1f2g3h4i5j6k7l8m9n0o1p2">
Il server Laravel verificherà che il valore di _token corrisponda a quello memorizzato nella sessione dell'utente corrente. Se l'attaccante provasse a inviare un form da un sito esterno, non conoscendo questo valore dinamico, la richiesta fallirebbe.
Esempio 2: Implementazione in un'architettura Single Page Application (SPA)
Nelle SPA (React, Vue, Angular) che comunicano con un'API REST, l'uso di form HTML classici è raro. In questo caso, si utilizza spesso il pattern Double Submit Cookie o l'invio del token tramite header personalizzati.
Il flusso tipico è:
- Il server invia un cookie (non
HttpOnly) contenente il token CSRF. - Il frontend legge il valore del cookie tramite JavaScript.
- Il frontend include questo valore in un header HTTP personalizzato (es.
X-CSRF-TOKEN) per ogni richiesta mutativa (POST, PUT, DELETE).
Ecco un esempio di come configurare Axios in React per includere il token:
import axios from 'axios';
import Cookies from 'js-cookie';
// Configurazione globale di Axios
axios.interceptors.request.use((config) => {
// Legge il token dal cookie 'XSRF-TOKEN'
const csrfToken = Cookies.get('XSRF-TOKEN');
if (csrfToken) {
// Aggiunge il token all'header della richiesta
config.headers['X-XSRF-TOKEN'] = csrfToken;
}
return config;
}, (error) => {
return Promise.reject(error);
});
// Esempio di chiamata API
const updateProfile = async (data) => {
try {
const response = await axios.post('/api/user/update', data);
console.log('Profilo aggiornato con successo!');
} catch (err) {
console.error('Errore CSRF o di rete', err);
}
};
In questo scenario, l'attaccante non può leggere il cookie XSRF-TOKEN a causa della Same-Origin Policy (SOP), che impedisce a uno script di un sito A di leggere i cookie di un sito B. Di conseguenza, non può costruire l'header X-XSRF-TOKEN corretto.
Esempi pratici e casi d'uso reali
Caso 1: Cambiamento Password
Senza CSRF token, un attaccante potrebbe creare una pagina che, una volta visitata, invia una richiesta a https://tuosito.com/account/change-password impostando una nuova password scelta dall'hacker. Se l'utente è loggato, la password verrebbe cambiata istantaneamente senza che l'utente se ne accorga.
Caso 2: Operazioni di E-commerce
Immaginate un pulsante "Acquista Ora" che invia una richiesta POST a /checkout/confirm. Un sito malevolo potrebbe forzare l'utente a cliccare su un link che triggera l'acquisto di un prodotto costoso o l'invio di un regalo a un indirizzo specifico, semplicemente sfruttando la sessione attiva dell'utente.
Caso 3: Amministrazione di Sistemi (CMS)
Nei pannelli di controllo (come WordPress o Joomla), un attacco CSRF potrebbe essere devastante. Un amministratore loggato che visita un sito compromesso potrebbe involontariamente creare un nuovo utente amministratore per l'attaccante, semplicemente perché il browser ha inviato i cookie di sessione di admin a una richiesta contraffatta verso /admin/create-user.
Errori comuni e FAQ
Errore: Usare GET per operazioni mutative
Uno degli errori più gravi è l'utilizzo del metodo GET per azioni che modificano lo stato del server (es. /logout?confirm=true o /delete-item?id=10). I token CSRF sono progettati per i metodi POST, PUT, DELETE. Le richieste GET sono, per definizione, idempotenti e non dovrebbero mai cambiare dati. Un attacco CSRF tramite GET è semplicissimo: basta un tag <img src="https://tuosito.com/delete-item?id=10"> e il browser eseguirà la richiesta tentando di caricare l'immagine.
FAQ: I token CSRF sono necessari se uso i JWT (JSON Web Tokens)?
Dipende da dove memorizzi il JWT.
- Se memorizzi il JWT nel
localStorage, non sei vulnerabile al CSRF (perché il browser non lo invia automaticamente), ma sei vulnerabile all'XSS (un malware potrebbe leggere il token). - Se memorizzi il JWT in un cookie
HttpOnly, sei protetto dall'XSS ma torni a essere vulnerabile al CSRF. In questo caso, i CSRF token sono assolutamente necessari.
FAQ: Qual è la differenza tra SameSite Cookie e CSRF Token?
L'attributo SameSite (Strict, Lax, None) nei cookie è una difesa moderna che istruisce il browser a non inviare i cookie in contesti cross-site.
SameSite=Strictimpedisce l'invio dei cookie in ogni richiesta proveniente da un altro sito.SameSite=Lax(default in molti browser moderni) permette l'invio solo per navigazioni top-level (link),
Sebbene SameSite offra un'ottima protezione, i CSRF token rimangono il "gold standard" perché forniscono una difesa esplicita e granulare, indipendente dalle implementazioni variabili dei browser.
Prossimi passi e approfondimenti
La protezione contro il CSRF è solo un tassello di una strategia di sicurezza più ampia. Per rendere la vostra applicazione davvero robusta, vi suggerisco di approfondire i seguenti temi:
- Content Security Policy (CSP): Per prevenire l'XSS, che potrebbe essere usato per bypassare i CSRF token leggendo i valori dai campi hidden.
- OAuth2 e OpenID Connect: Per gestire l'autenticazione in modo standardizzato e sicuro.
- OWASP Top 10: Studiate la guida di OWASP per conoscere le dieci vulnerabilità web più critiche e come mitigarle.
- HTTP-Only e Secure Cookies: Imparate a configurare i cookie per evitare che vengano letti via JavaScript o trasmessi su connessioni non cifrate (HTTP).
In conclusione, l'implementazione dei CSRF token non è un optional, ma una necessità per ogni applicazione che gestisca sessioni utente. Che stiate usando un framework moderno che lo faccia per voi o che stiate costruendo un sistema custom, assicuratevi che ogni azione che modifica i dati richieda una prova di intenzionalità che solo l'utente legittimo può fornire.