Modulo di autenticazione

Considerazioni sulla sicurezza durante l'autenticazione

Uno dei vettori più prolifici per le vulnerabilità di sicurezza nelle applicazioni e nei siti Web è l'autenticazione: il login. Ottenere l'accesso a dati e funzionalità importanti potrebbe essere molto redditizio, quindi compromettere le credenziali degli utenti è un obiettivo primario poiché aver visto in passato. Ecco un paio di insidie ​​​​a cui prestare attenzione durante la progettazione dell'interfaccia di autenticazione.

Controlli password corretti

Costringi i tuoi utenti a selezionare password complesse! Una buona policy per le password rende impraticabili gli attacchi di forza bruta e di dizionario, ovvero indovinare la password di un utente con mezzi manuali o automatizzati. La restrizione più importante è impostare la lunghezza minima consentita a 8 caratteri e ciò dovrebbe essere fatto sempre. Oltre a questo, ecco alcune restrizioni comuni che tentano di garantire una password adeguata con una complessità minima:

  • Un misto di lettere maiuscole e minuscole.
  • Un misto di lettere e numeri.
  • Almeno un carattere speciale: !@#$%^&*() ecc.

Tuttavia, è stato trovato da ricerca sponsorizzata dal NIST statunitense ¹ che gli utenti tendono a rispondere a queste restrizioni aggiuntive in modo molto prevedibile. Ad esempio, se un utente intendeva utilizzare la password "password", ma incontrava le restrizioni di cui sopra, probabilmente cambierebbe la sua password con la prevedibile "Password1!".
Il NIST suggerisce di confrontare la password con una lista nera di password inaccettabili e di rifiutare le password che corrispondono. Citazione:

Questo elenco dovrebbe includere le password dei precedenti corpus di violazioni, parole del dizionario e parole specifiche (come il nome del servizio stesso) che gli utenti probabilmente sceglieranno. Poiché anche la scelta delle password da parte dell'utente sarà regolata da un requisito di lunghezza minima, questo dizionario deve includere solo le voci che soddisfano tale requisito.

NIST
Messaggio di errore che richiede all'utente di scegliere una password più sicura

Implementarlo da soli può essere un compito arduo, ma fortunatamente ci sono servizi e librerie progettati per aiutarti. Il zxcvbn libreria disponibile in molte lingue è un'opzione. Il pwned Il database delle password contiene centinaia di milioni di password craccate, rispetto alle quali è possibile verificare la password scelta dall'utente. Puoi ospitarlo localmente o utilizzare un file API per comunicare con esso (usando gli hash ovviamente!).

Se ti aspetti che i tuoi utenti parlino lingue che utilizzano un alfabeto non latino o un latino esteso (con segni diacritici o lettere speciali come ø, æ, œ), teoricamente consentire l'intera gamma di caratteri Unicode migliorerà la sicurezza. Ciò impedirà la maggior parte degli attacchi di forza bruta, che presuppongono comunque gli standard ASCII, e aumenterà anche enormemente il numero di combinazioni possibili². Anche se questo potrebbe sembrare un gioco da ragazzi, in pratica, devi tenere conto del fatto che potresti non riuscire a gestire correttamente UTF-8 (ad esempio, gestire caratteri canonicamente equivalenti). Una gestione impropria di Unicode potrebbe ridurre l'usabilità e persino avere un impatto negativo sulla sicurezza. Fallo solo se sei sicuro delle tue capacità.
Come ultima nota, è importante impostare un limite massimo di lunghezza. Non farlo potrebbe esporti a a password lunga DOS attacco. 64 caratteri è un buon limite massimo.

Hashing delle password

La prima regola per l'archiviazione delle credenziali è di non archiviare mai le password non elaborate sul server. Memorizza invece gli hash per l'autenticazione. L'hashing è un metodo molto migliore rispetto alla limitazione dell'uso di determinati caratteri speciali.

Per mettere le cose in chiaro: l'hashing lo è non crittografia. L'hashing è una funzione unidirezionale che associa ogni password a un hash univoco o quasi sempre unico (sì, sono stati scoperti hash in conflitto con alcune funzioni hash, e questo è un attacco vettoriale, quindi la scelta della funzione è importante). Considerando che la crittografia può essere invertita per ottenere la password originale. A tal fine, le password non elaborate non dovrebbero mai essere archiviate crittografate sul server.

Per memorizzare una password, dovrebbe essere sottoposta a hashing sul server (potrebbe arrivare fino a 10,000 iterazioni). L'hashing lato client ha solo vantaggi marginali; un utente malintenzionato man-in-the-middle (MITM) può semplicemente prendere l'hash e inviarlo al server per accedere proprio come un utente legittimo. Potrebbe impedire la fuoriuscita della password effettiva e il suo riutilizzo per aprire altri servizi, ma per la sicurezza specifica dell'app, ha un effetto limitato mentre aggiunge complessità. L'hashing lato server è più potente poiché un utente malintenzionato non conosce la tua configurazione di hashing sul server, quindi anche se tutti i tuoi hash sono trapelati, non può comunque autenticarsi. Questo dovrebbe essere considerato obbligatorio.

Anche se vengono eseguiti tutti i passaggi precedenti, è importante prepararsi alla possibile causa della perdita del database insieme agli hash: un utente malintenzionato può quindi forzare gli hash. Questo è fatto da

  1. Hashing di una password candidata.
  2. Verifica se l'hash corrisponde a una password nel database.

La salatura degli hash è quindi importante. Il salting concatena una stringa casuale alla password, esegue l'hashing del risultato e quindi archivia l'hash insieme al salt nel database. L'attaccante non è quindi in grado di controllare l'hash di una possibile password rispetto a ogni hash nel database. Invece, deve aggiungere il valore salt di un utente alla password proposta, eseguirne l'hash, quindi testarla rispetto all'hash dell'utente, costringendolo a decifrare un hash alla volta. Le moderne funzioni di hashing delle password di solito salano automaticamente l'input prima dell'hashing.

La migliore allungamento chiave algoritmi disponibili oggi per le password sono Argon2id ³, che ha vinto il 2015 Concorso di hashing delle passworde PBKDF2 , raccomandato dal NIST. Questi usano semplici funzioni di hashing comuni come SHA256 come "backend", ma le applicano migliaia di volte e aggiungono automaticamente sale. Perciò, non si usa direttamente semplici funzioni di hashing come SHA o MD5 non progettate per le password, né tenta di costruire i tuoi metodi di allungamento delle chiavi, è una commissione da pazzi.

TLS e autenticazione

Un'altra precauzione obbligatoria durante l'autenticazione è garantire che l'intera transazione passi su TLS, il che rende gli attacchi MITM HTTP molto più difficili richiedendo certificati.

TLS (Transport Layer Security) è il protocollo successore di SSL (Secure Socket Layer). In breve, TLS include innanzitutto una procedura di handshake che richiede al server di inviare un certificato da un'autorità attendibile che ne garantisca la legittimità. Le chiavi di crittografia vengono quindi scambiate utilizzando il file Metodo Deffie-Hellman (viene generata sia la chiave pubblica che quella privata). Il resto della sessione viene quindi crittografato e decrittografato utilizzando queste chiavi.

Come implementare correttamente TLS per il tuo servizio è un argomento in sé e per sé, quindi non ne parleremo oggi, ma puoi leggere tu stesso il best practice.

Autenticazione a più fattori

Di gran lunga, il modo migliore per prevenire incidenti di sicurezza della password è raccomandare o richiedere l'autenticazione a più fattori (MFA). Ciò significa utilizzare uno o più altri metodi per autenticare l'utente. Ciò può includere l'utilizzo di e-mail, SMS, codici QR, scansioni biometriche e app di autenticazione. Il modo più comune per farlo è utilizzare e-mail o SMS per inviare all'utente un OTP che dovrà inserire per ottenere l'accesso. Un utente malintenzionato dovrebbe quindi avere accesso all'e-mail dell'utente per ottenere l'accesso al tuo servizio.
Questo apre altri vettori di attacco, ma per la maggior parte delle applicazioni dovrebbe essere sufficiente.

Un modo per ridurre l'overhead dell'autenticazione a più fattori sia per lo sviluppatore che per l'utente consiste nel richiedere l'autenticazione a più fattori solo durante le transazioni importanti. Ad esempio, quando

  • Modifica delle password.
  • Esecuzione di transazioni monetarie.
  • Disattivazione dell'autenticazione a più fattori.
  • Ottenere l'accesso come amministratore.

È anche possibile imporre MFA solo ogni paio di giorni, dopo un lungo periodo di inattività o dopo tentativi di accesso falliti (con il vantaggio di prevenire la forza bruta). 

Prevenzione dei bot

Un modo per impedire tentativi di accesso automatizzati, sia come parte di un attacco di forza bruta che di DOS, consiste nell'implementare i timeout dopo diversi tentativi falliti. Ciò fa sì che gli attacchi di forza bruta e dizionario richiedano semplicemente troppo tempo per essere fattibili. Un'altra possibilità è richiedere all'utente di risolvere un captcha durante ogni accesso, o magari dopo un numero prestabilito di tentativi falliti. 

risolto reCAPTCHA
reCAPTCHA

Forse il servizio captcha di terze parti più popolare è reCAPTCHA di Google, ma ha visto qualche critica per le sue politiche sulla privacy. reCAPTCHA costringe anche efficacemente i tuoi utenti ad accettare un accordo sui termini di servizio di terze parti per utilizzare il tuo servizio, che alcuni considerano eticamente problematico. Tuttavia, l'utilizzo di reCAPTCHA è probabilmente il modo più semplice e affidabile per prevenire spam o bot di forza bruta. Esistono altre soluzioni Captcha online, come hCaptcha, ma come soluzioni di terze parti, potrebbero tutte soffrire degli stessi problemi di reCAPTCHA.

Messaggi di errore

Per impedire agli aggressori di determinare se un nome utente è valido o meno, è considerata una buona pratica avere un unico messaggio di errore ambiguo, indipendentemente dal fatto che l'errore sia dovuto al fatto

  • La password non era corretta.
  • L'account non esiste.
  • L'account è bloccato, disabilitato o bannato.

Un messaggio di errore corretto e sicuro sarebbe qualcosa di simile

"Login failed; Invalid user ID or password."

Inoltre, il ripristino dell'account non dovrebbe mai riconoscere se il processo è stato un successo o meno, ma piuttosto inviare risposte come

"If that email address is in our database, we will send you an email to reset your password."

E altre ancora…

"A link to activate your account has been emailed to the address provided."

Rispettivamente.

Anche se i tuoi messaggi di errore sono vaghi, un aggressore tenace potrebbe tentare di confrontare le discrepanze dei tempi di risposta per determinare se la password è errata o se l'account non esiste. Ad esempio, questo pseudo-codice

if (user_exists(username)) {
    String password_hash = hash(password);
    bool valid = lookup_credentials(username, password_hash);
    if (!valid) {
        return Error("Invalid Username or Password!");
    }
} else {
   return Error("Invalid Username or Password!");
}

Ritornerà più velocemente se l'utente non esiste, che l'attaccante può sfruttare, mentre questo codice

String password_hash = hash(password);
bool valid = lookup_credentials(username, password_hash);
if (!valid) {
   return Error("Invalid Username or Password!");
}

Ci vorrà all'incirca la stessa quantità di tempo in entrambi i casi.

Autenticazione Single Sign-On

Negli ultimi anni è diventato di moda un metodo di autenticazione noto come Single Sign-On. Quando un utente vuole accedere al tuo servizio, gli viene chiesto di scegliere un provider di identità fidato, di solito una mega-società che offre quel servizio, come Google, Amazon o Facebook. L'utente viene quindi reindirizzato al sito Web del provider di identità e deve accedere utilizzando il proprio account lì. Il provider di identità invia quindi al tuo servizio una garanzia che l'utente è autorizzato, solitamente sotto forma di uno schema XML, che fornisce anche dettagli sull'identità dell'utente. Ci sono un paio di protocolli per farlo, ma il più popolare è di gran lunga OpenID.

Il vantaggio principale è la facilità d'uso. L'utente deve solo mantenere un singolo account con un'unica password per accedere a molti servizi diversi. Inoltre, se l'utente è già loggato con quell'account, non è necessario inserire nuovamente le proprie credenziali. Ciò consente inoltre all'utente di ricordare un'unica password per tutti i suoi account. Un altro vantaggio per lo sviluppatore è la riduzione della necessità di occuparsi personalmente dell'autorizzazione. Piuttosto, lo deleghi a terze parti fidate come Google.

Ma ci sono alcuni inconvenienti di sicurezza in questo approccio: il più evidente è il fatto che se l'account del provider di identità dell'utente viene violato, l'aggressore può accedere anche a una miriade di altri servizi. Ciò ha lo stesso effetto dell'utilizzo della stessa password e nome utente su tutti gli account, una pratica che viene comunque eseguita da molti utenti.

Molti siti Web moderni offrono spesso sia SSO che account tradizionali a servizio singolo, lasciando la scelta all'utente.


¹ Istituto nazionale degli standard e della tecnologia


² Sono disponibili 143,859 caratteri Unicode. Ciò significa che ci sono un massimo di 143,8598 1.83×1041 possibili combinazioni di 8 caratteri. Confronta con il 1288 = 7.2 × 1016 combinazioni di ASCII.

³ ragon2id deve essere utilizzato con le seguenti configurazioni: 

  • m=37 MiB, t=1, p=1
  • m=15 MiB, t=2, p=1

Dove m è la memoria minima, t è il numero di iterazioni e p è il grado di parallelismo. Sono equivalenti in termini di sicurezza, ma il primo è più affamato di memoria ma meno esigente per il processore e viceversa. Ricordati di usare Aragon2id variante.

⁴Durante l'hashing con PBKDF2, il numero di iterazioni deve essere impostato in base all'algoritmo di hashing interno utilizzato. I seguenti suggerimenti sono equivalenti in termini di sicurezza:

  • PBKDF2-HMAC-SHA1: 720,000 iterazioni
  • PBKDF2-HMAC-SHA256: 310,000 iterazioni
  • PBKDF2-HMAC-SHA512: 120,000 iterazioni
Salta al contenuto