Salvataggio delle password nel database

Recentemente ho dovuto realizzare un piccolo portale di Single-Sign-On, una webapp sfruttata per collegarsi a varie applicazioni messe a disposizione dall’azienda. Uno dei requisiti di questo portale doveva essere che un utente potesse accedere tramite una autenticazione LDAP oppure tramite credenziali salvate nel database (per scenari dove il server LDAP non fosse disponibile).

application workflow

Vediamo insieme quali punti ho tenuto in considerazione per gestire questo progetto

Cifratura (Hashing)

Le password ovviamente non vanno salvate in chiaro, questo perché in caso di furto del database tutte le credenziali sarebbero compromesse, mentre noi dobbiamo garantire la sicurezza delle credenziali anche in questo scenario.

La prima cosa da fare è quindi di salvare in modo sicuro la password nel database. Durante la fase di login il sistema cifrerà la password inserita dall’utente e la confronterà con quella salvata nella nostra base dati.

Per cifrare la password in chiaro useremo un algoritmo di hashing, che ha la caratteristica di essere monodirezionale: dato un hash non si può risalire al testo originale, molto importante perchè se qualcuno rubasse il database, senza questo algoritmo potrebbe “ricalcolarsi” le password. Il risultato sarà come un’impronta digitale, univoca e si chiamerà fingerprint.

application workflow

Algoritmo di hashing

Esistono molti algoritmi di hashing al giorno d’oggi ma non tutti sono uguali. Nell’applicazione che ho realizzato ho voluto lasciarmi aperta la possibilità di variare l’algoritmo in un secondo momento aggiungendo una chiave di configurazione che indichi quale algoritmo utilizzare. Questo aspetto mi aiuterà anche a gestire, in modo graduale, la migrazione da un “vecchio” algoritmo ad uno più “nuovo”.

Un algoritmo molto utilizzato è bCrypt in quando è molto lento ed in casi di attacchi brute-force rallenterà di molto il processo di hacking.

Salting

Ora che abbiamo la password cifrata abbiamo però un altro problema: se qualcuno ci rubasse il database e avesse un’elenco di testo-fingerprint potrebbe dedurre quali sono le password dei nostri utenti. Questo elenco è chiamato “rainbow table” ed è efficace perchè l’hash di “Pippo111” (usando lo stesso algoritmo, per esempio SHA256) sarà sempre 426ecb09f1c4fc39d4f66d416c6356cb182dc6247bb3d59341ab3b054c1cc7bb.

Per “risolvere” questo problema utilizzeremo una stringa di testo randomica da aggiungere prima o dopo la nostra password (prima di effettuare l’hashing). Questa stringa di testo (diversa per ogni utente) si chiama Salt: in questo modo due utenti con la stessa password avranno fingerprint differenti e sarà molto meno probabile trovare un match all’interno di una rainbow table.

Il salt andrà salvato nel database poichè l’applicazione dovrà usarlo per generare il fingerprint di verifica delle credenziali.

application workflow

Per aumentare ulteriormente la sicurezza, nella mia applicazione, ho ricalcolato il salt ad ogni cambio / reset password.

HTTPS

La connessione verso il portale SSO che ho realizzato era solamente in HTTPS, un aspetto quasi normale al giorno d’oggi, ma talvolta dimenticato in piccole realtà aziendali. Un canale HTTPS ci permette di evitare che qualcuno possa intercettare il payload durante la fase di login, leggendo le credenziali inserite dall’utente.

Parametrizzare le query

Un altro aspetto importante riguarda il come andiamo ad eseguire le query nella nostra applicazione. Nel mio caso l’applicazione era stata sviluppata in .NET Core e sfruttavo Entity Framework per andare ad eseguire le query su database. Nel caso in cui volessimo scrivere noi una query è sempre importante passare i parametri come SqlParameter e non includendoli nella query, ecco un esempio:

SBAGLIATO

C#
using (SqlCommand cmd = new SqlCommand("", conn))
{
    cmd.CommandText = @"          
    Select 
        username, 
        salt, 
        algorithmType, 
        password
    from T_Credentials 
    where username = '"+ varUsername +"'";

    using (SqlDataReader reader = cmd.ExecuteReader())
    {
        //...
    }
}

CORRETTO

C#
using (SqlCommand cmd = new SqlCommand("", conn))
{
    cmd.CommandText = @"          
    Select 
        username, 
        salt, 
        algorithmType, 
        password
    from T_Credentials 
    where username = @username";
    cmd.Parameters.AddWithValue("@username", varUsername);

    using (SqlDataReader reader = cmd.ExecuteReader())
    {
        //...
    }
}

In questo modo eviteremo attacchi di Sql Injection. In aggiunta a questo la mia applicazione aveva anche una validazione dell’input in modo tale che la variabile con l’username arrivasse qui già “pulita”.

Blocco dopo X tentativi

Per evitare attacchi bruteforce ho previsto due livelli di sicurezza, uno a livello applicativo e uno a livello di utenza. Il primo monitora il flusso di chiamate da uno specifico client, nel caso in cui si rilevi un numero anomalo di richieste è possibile prevedere un blocco automatico di X minuti.

Per quanto riguarda l’utenza ho previsto un blocco della stessa dopo 5 tentativi falliti di login (parametro gestito nella configurazione). Nella tabella delle credenziali ho aggiunto un campo per conteggiare il numero di tentativi falliti ed un campo che indichi lo stato dell’utenza (attiva, disattiva, sospesa, scaduta,..).

Il contatore degli accessi falliti si resetterà nel momento in cui viene effettuato un login corretto.

Conclusioni

In questo articolo abbiamo visto quali strategie ho utilizzato per poter gestire la sicurezza delle credenziali salvate su database. I punti da tenere in considerazione sono i seguenti:

  • Mai salvare le password in chiaro nel database
  • Calcolare l’hash aggiungendo sempre dopo la password una stringa random (salt)
  • Utilizza un algoritmo di hashing lento, in modo tale da allungare i tempi in caso di attacco
  • Utilizza sempre l’HTTPS
  • Utilizza sempre i parametri nelle query per evitare attacchi di Sql Injection
  • Strategie di blocco in caso di attacchi bruteforce

Il workflow generale dal punto di vista applicativo è invece il seguente:

Workflow applicazione

Se questo articolo ti è piaciuto non dimenticarti di condividerlo, alla prossima!

Condividi questo articolo
Shareable URL
Post precedente

HashSet<T> in .NET 9

Prosimo post

.NET 9 Hybrid Cache

Lascia un commento

Il tuo indirizzo email non sarà pubblicato. I campi obbligatori sono contrassegnati *

Leggi il prossimo articolo