Esiste una categoria di attacchi sorprendente: non sfrutta un bug nel codice, ma il tempo che il sistema impiega a rispondere. Misurando i millisecondi, un attaccante può estrarre un segreto. Si chiamano timing attack, e si neutralizzano con un confronto "a tempo costante".
Cos'è un timing attack
Immagina che il sistema confronti un token carattere per carattere e si fermi al primo che non combacia. Più caratteri iniziali sono corretti, più tempo impiega. Misurando quei tempi, l'attaccante indovina il segreto un pezzo alla volta.
Perché il confronto "normale" è vulnerabile
Il confronto ingenuo (che si interrompe appena trova una differenza) "perde tempo" in modo misurabile e correlato al segreto. È proprio questa correlazione a essere sfruttabile.
Il confronto a tempo costante
La soluzione è confrontare i valori in un tempo che non dipende da quanto combaciano. In Node.js esiste una funzione apposita:
const crypto = require('crypto');
crypto.timingSafeEqual(Buffer.from(a), Buffer.from(b));
Il tempo è sempre lo stesso, quindi non rivela nulla.
Dove si applica
Ovunque si confronti un segreto: token di sessione, firme, chiavi API, codici di verifica. È un accorgimento piccolo ma che distingue il software fatto con cura.
Il confronto costante da solo non basta
Due dettagli operativi: timingSafeEqual richiede buffer della stessa lunghezza e solleva un'eccezione altrimenti, quindi si confrontano valori a lunghezza fissa (per esempio l'hash del token, non il token grezzo). E il tempo può rientrare dalla finestra: se un token valido innesca una query al database e uno inesistente no, la differenza torna misurabile a livello di richiesta, indipendentemente da come è stato fatto il confronto.
Collegato: hash delle password e rate limiting.