S.L.A.SH Project: Autenticazione e sicurezza
La decisione di dotare lo SLASH Project di un sistema di registrazione e login utenti, ha sollevato alcune problematiche relative alla sicurezza.
La “questione della sicurezza” è un argomento vasto e complesso, che analizza il problema da diversi punti di vista, e che, indubbiamente, meriterebbe un approfondito ed esclusivo studio.
Lo sviluppo di tale argomento non fa parte di questa trattazione, però è stato inevitabile operare una ricerca sulle problematiche più frequenti, in modo da garantire, all’utenza dello SLASH Project, l’utilizzo di un sistema che risulti sufficientemente protetto.
La realizzazione di un sistema di login e registrazione utenti per un’applicazione web prevede diversi passaggi, ognuno dei quali in un preciso contesto applicativo. Di seguito andrò ad elencare i passaggi che possono presentare eventuali punti critici “di base”:
Registrazione utente:
- All’utente viene proposta una pagina HTML dotata di form in grado di accogliere i dati necessari alla registrazione;
- I dati raccolti dal form vengono inviati al server che deve procedere al salvataggio sul database.
Login utente:
- All’utente viene proposta una pagina HTML dotata di form in grado di accogliere i dati richiesti per il login (ad esempio: email e password);
- I dati raccolti dal form vengono inviati al server;
- Il CMS estrae dal database il record relativo all’utente che fa richiesta di login. Segue poi il controllo sull’effettiva esistenza dell’utente e successivamente la corrispondenza tra la password inserita e quella presente sul database. Se i controlli hanno esito positivo, verrà garantito l’accesso all’utente che ne ha fatto richiesta, altrimenti verrà visualizzato un messaggio di errore.
Sessione autenticata:
- A seguito dell’autenticazione dell’utente sarà necessario aggiornare l’array di sessione in modo da tenere traccia del login avvenuto, permettendo così all’utente di accedere a tutte le pagine “private” senza dover effettuare ogni volta un nuovo login;
Dopo aver illustrato i passaggi essenziali necessari per registrazione e login utente, appaiono evidenti i blocchi di operazioni che possono presentare dei rischi.
Di seguito andrò a sintetizzare queste criticità e successivamente presenterò le soluzioni per garantire un sufficiente livello di sicurezza.
1.Compilazione del form ed invio dei dati al server;
2.Salvataggio sul database dei dati inseriti dall’utente;
3.Conservazione delle password all’interno del database;
4.Sicurezza della sessione contenente i dati che validano il login.
Compilazione del form e invio dei dati al server
Il punto critico in questo blocco di operazioni è costituito dall’invio dei dati al server.
Evitando di dare per scontato alcunché, la prima precisazione da fare è che il metodo di invio dei dati da parte del form dovrà essere impostato METHOD = “POST”, evitando così di utilizzare l’URL per trasmettere dati evidentemente sensibili, come la password ad esempio.
In questo modo eviteremo scenari del tipo:
L’utente inserisce email e password per l’autenticazione e una volta ottenuta la conferma di login salva la pagina nei preferiti. Se la pagina contenente il form invia i dati attraverso il metodo GET, l’utente finirà per salvare tra i preferiti un URL di questo tipo:
http://www.miosito.org/login.php?utente=Pippo&pwd=PasswordDiPippo.
Utilizzando il metodo POST ci assicuriamo pertanto che le informazioni inviate al server non siano visibili in chiaro nell’URL.
Il protocollo HTTP, però, non offre nessuna cifratura dei dati e, pertanto, questi verranno inoltrati al server attraverso pacchetti “in chiaro”, senza quindi alcuna protezione che possa salvaguardare la comunicazione da eventuali attacchi che prevedono l’intercettazione dei pacchetti che transitano sulla rete.
A questo punto si pone il problema: la password va criptata prima di essere inviata al server?
A seguito delle considerazioni fatte, sembra lecito pensare che cifrare la password prima di inviarla al server sia un’ottima misura di sicurezza. Purtroppo non è così.
Supponiamo di cifrare la password prima dell’invio al server: è vero che la parola chiave scelta dall’utente non sarà leggibile in chiaro, ma è anche vero che la nuova password generata a seguito della cifratura diventerà, in questo scenario, l’effettiva password che inviamo al database per il salvataggio.
In sostanza non si è aggiunto alcun livello di sicurezza, abbiamo soltanto “cambiato” la password dell’utente rendendola cifrata e illeggibile, ma se qualcuno se ne appropriasse sarebbe comunque in grado di accedere al database attraverso la password cifrata (che, come dicevo prima, risulta ora essere la vera password dell’utente).
Molto importante in questo scenario è anche il fatto che la cifratura lato client viene necessariamente eseguita attraverso uno script, ad esempio Javascript, e che l’esecuzione di tale script può essere aggirata senza grandi difficoltà. Qui di seguito un esempio:

Come evidenziato dall’immagine d’esempio, il reale punto critico è il trasferimento http. Nel caso di intercettazione dei dati in transito dal client al server verrebbe prelevata la password in chiaro.
Nel caso di assenza di cifratura lato client, la password sarebbe anche leggibile, ma questo poco importa ad un utente malintenzionato, perché egli, una volta in possesso della password cifrata, avrebbe la possibilità di bypassare con poche difficoltà lo script Javascript ed inviare pertanto al server una password assolutamente valida.
La conclusione è che per garantire la piena sicurezza dei dati scambiati tra client e server è necessario utilizzare una connessione protetta, attraverso il protocollo HTTPS.
Salvataggio sul database dei dati inseriti dall’utente
Quando si procede al salvataggio dei dati inseriti dall’utente è essenziale assicurarsi che all’interno dei campi del form non venga immesso del codice malevolo con l’intenzione di farlo eseguire dal server di database.
Questo tipo di attacco prende il nome di SQL injection.
L’SQL injection sfrutta le vulnerabilità di sicurezza del software di un’applicazione, ad esempio, quando l’input dell’utente non è correttamente filtrato da 'escape characters' contenuti negli statement SQL oppure non è fortemente tipizzato e viene eseguito inaspettatamente. L’SQL injection è più conosciuto come attacco per i siti web, ma è anche usato per attaccare qualsiasi tipo di database SQL.
La radice del problema delle SQL injection è che codice e dati vengono “mischiati”. Una query SQL infatti, è paragonabile ad un vero e proprio “programma”, ed aggiungendo dati esterni alle istruzioni che si intendono eseguire in sostanza stiamo creando questo “programma” dinamicamente. In questo caso i dati aggiunti possono interferire con il codice da eseguire ed anche alterarlo.
Questo codice:
$expected_data= 1;
$query= “SELECT * FROM articles where id = $expected_data;
produrrà esattamente la query attesa:
SELECT * FROM articles WHERE id = 1
Mentre invece questo codice:
$spoiled_data= “1; DROP TABLE articles;”
$query= “SELECT * FROM articles where id = $spoiled_data”;
eseguirà una query contenente codice malevolo:
SELECT * FROM articles WHERE id = 1; DROP TABLE articles;
L’istruzione precedente funziona perché stiamo aggiungendo i dati direttamente al codice da eseguire ed inviamo tutto al server che eseguirà quanto ricevuto.
La soluzione è quella di separare il codice dai dati.
Attraverso i “prepared statement” è possibile inviare al server prima di tutto il codice da eseguire sostituendo i dati con dei segnaposto (placeholder) e solo successivamente indicare i valori effettivi che andranno a rimpiazzare i placeholder utilizzati.
In questo modo invieremo innanzitutto un modello di istruzione da eseguire e solo successivamente andremo ad associare ai placeholder i dati inseriti dall’utente, evitando che essi possano influenzare l’istruzione da eseguire e difendendo, quindi, il nostro database da attacchi di tipo SQL injection.
Infine, nonostante il livello di sicurezza offerto dai prepared statement, è buona norma, in fase di validazione dei dati del form, “bonificare” le stringhe rimuovendo o neutralizzando eventuali caratteri o sequenze che possano favorire questo tipo di attacchi o altri, sfruttando le varie funzioni offerte dal PHP stesso (ad esempio trim(), stripslashes() e htmlspecialchars()).
Conservazione delle password all’interno del database
Quando si procede alla registrazione di un nuovo utente, è indispensabile che il salvataggio sul database avvenga in modo sicuro per garantire la protezione della chiave di accesso anche in seguito ad un eventuale furto delle informazioni presenti sulla base di dati.
Proprio per questo motivo la password deve essere cifrata prima di procedere al salvataggio sul database, e la cifratura avviene attraverso l’utilizzo di un algoritmo di hash.
Gli algoritmi di hash sono funzioni unidirezionali che restituiscono una stringa di numeri e lettere a partire da un qualsiasi flusso di bit di qualunque dimensione. L’output ottenuto viene chiamato digest e può essere considerato come l’impronta digitale del flusso di bit che l’ha originato. Gli algoritmi di hash hanno la peculiarità di fornire un output totalmente diverso anche partendo da flussi che presentano differenze minime.
La cifratura attraverso un algoritmo di hash non è però sufficiente a garantire la sicurezza delle password conservate all’interno del database, e per capirne le ragioni è necessaria una brevissima panoramica dei tipi di attacco più comuni.
Attacco a dizionario e attacco a forza bruta.
L’attacco a dizionario utilizza un file contenente parole, frasi, password comuni ed altre stringhe che è probabile vengano utilizzate come password. Per ogni stringa del file viene generato un hash ed esso viene confrontato con l’hash della password da trovare. Se i due hash coincidono la password è stata trovata.
L’attacco a forza bruta, invece, confronta l’hash della password con quelli generati usando una combinazione di caratteri (data una certa lunghezza).
Entrambi gli attacchi risultano molto dispendiosi in termini di tempo e di potenza di calcolo.
Lookup tables, Reverse lookup tables, Rainbow Tables.
Le lookup tables sono un metodo molto efficiente per violare molti hash dello stesso tipo molto velocemente. L’idea è quella di pre-calcolare gli hash delle password presenti in un “dizionario” ed immagazzinarle, insieme alla password corrispondente, in una lookup table. Una look up table ben implementata può permettere il processamento di centinaia di hash per secondo.
Con le reverse lookup tables, invece, è possibile eseguire un attacco a dizionario o a forza bruta su molti hash allo stesso momento, senza aver bisogno di pre-calcolare una tabella di lookup. Chi effettua l’attacco crea una lookup table che mappa ogni hash delle password contenute nel database ad una lista di utenti che hanno quel determinato hash. A questo punto gli hash generati da chi effettua l’attacco (a dizionario o a forza bruta) vengono confrontati con quelli presenti nella tabella di lookup per ottenere una lista degli utenti per i quali l’hash della password è uguale all’hash generato dall’attaccante.
Questo attacco risulta particolarmente efficace siccome accade spesso che molti utenti utilizzino la stessa password.
Le rainbow tables sono tabelle di associazione che offrono un compromesso tempo-memoria. Vengono utilizzate in modo analogo alle lookup tables, ma sono strutturate in maniera diversa, privilegiando l’efficienza in termini di spazio e permettendo quindi di conservare molti più hash nella stessa quantità di memoria.
A seguito di questa panoramica, appare chiaro che le lookup tables e le rainbows tables funzionano proprio perché due password uguali restituiranno sempre lo stesso hash. E’ evidente, quindi, la necessità di rendere “unici” anche gli hash di password uguali.
Per ottenere questo risultato possiamo aggiungere alla password una stringa generata casualmente, che in crittografia viene chiamata salt (sale), e successivamente applicare l’algoritmo di hash alla password + salt.
In questo modo due password uguali verranno comunque abbinate a due salt differenti, essendo essi generati casualmente, e pertanto gli hash derivanti saranno inevitabilmente diversi, rendendo inefficaci le lookup tables (questo perché non è possibile stabilire a priori il valore del salt per pre-calcolare gli hash da immagazzinare nelle lookup tables).
In fase di salvataggio della password verranno quindi salvati due dati: l’hash della password e il salt utilizzato. Non è necessario che il salt sia segreto, perché, come detto poco sopra, è impossibile pre-calcolare gli hash senza conoscere il salt.
Nel contesto di questo progetto, ho optato per l’utilizzo di una funzione di password hashing fornita dal PHP: password_hash() utilizzata in abbinamento alla sua controparte password_verify().
La funzione password_hash() utilizza di default l’algoritmo di hash BCRYPT, genera automaticamente il salt da abbinare alla stringa in input e restituisce un output comprensivo del salt generato. Ritengo, in questo modo, di aver adottato tutte le misure necessarie a garantire un sufficiente livello di sicurezza per la conservazione della password sul database.
Sicurezza della sessione contenente i dati che validano il login
A seguito di una richiesta di login conclusasi con successo, si presenta la necessità di conservare, all’interno dell’array di sessione, i dati necessari a validare l’avvenuto login dell’utente, in modo da non dover chiedere nuovamente l’inserimento della password ad ogni richiesta di accesso a una pagina protetta.
La sessione, però, presenta alcune criticità, siccome può essere oggetto di attacchi cosiddetti “session hijacking” (dirottamento di sessione).
Di seguito una breve panoramica dei metodi principali che permettono di effettuare un dirottamento di sessione:
Session fixation: in questo caso chi effettua l’attacco imposta l’ID di sessione dell’utente su un ID conosciuto, per esempio inviando alla vittima un’email con un link contenente un particolare ID di sessione. A questo punto deve solo attendere che l’utente effettui il login per impadronirsi di una sessione valida e autenticata;
Session sidejacking: questo metodo prevede l’utilizzo di tecniche di packet sniffing per intercettare il traffico di rete tra il computer della vittima e il server. Molti siti web utilizzano la cifratura TLS solo per le pagine di login e non per il resto del sito una volta effettuata l’autenticazione. Questo permette ad un soggetto malintenzionato di intercettare il traffico generato tra client e server, e, siccome in questo traffico è compreso il cookie di sessione, impadronirsi di una sessione valida e autenticata;
Cross-site scripting: gli attacchi cross-site scripting usano vulnerabilità note delle applicazioni web, nei loro server o dei plugin su cui si basano. Sfruttando una di queste vulnerabilità, l’aggressore inietta contenuto malevolo nel contenuto fornito dal sito compromesso, ingannando il computer della vittima e riuscendo ad ottenere una copia del cookie di sessione impadronendosi, quindi, di una sessione valida e autenticata.
Sulla base di quanto descritto andrò ad elencare quali possibili accortezze si possono adottare per limitare in parte i rischi derivanti dall’utilizzo di una sessione.
Considerando che buona parte degli attacchi si basano sul “furto” della sessione, una buona misura di sicurezza è rigenerare l’ID di sessione ed eliminare quello vecchio ogni volta che l’utente cambia il suo livello di accesso (ad esempio: login, logout, accesso amministrativo), in questo modo un ID di sessione precedentemente sottratto risulterebbe inservibile.
Il PHP mette a disposizione la funzione session_regenerate_ID($delete_old_session=false). Attraverso questa funzione verrà rigenerato l’ID di sessione ad ogni cambio di accesso dell’utente e, passando il parametro TRUE, verrà cancellato il vecchio ID di sessione.
Ci assicureremo, inoltre, che nel file di configurazione di PHP session.use_only_cookies sia impostato a “1”, così facendo il modulo userà solo i cookie per salvare l’ID di sessione sul client, rendendo inutilizzabili gli identificativi di sessione passati nelle URL.
In questo modo innalzeremo il livello di protezione contro attacchi di tipo Session fixation.
Per limitare, invece, la vulnerabilità ad attacchi XSS (Cross Site Scripting) andremo a modificare un’altra impostazione del file di configurazione: session.cookie_httponly che contrassegna il cookie come accessibile solo attraverso il protocollo HTTP. Questo significa che il cookie non sarà accessibile dai linguaggi di scripting, come Javascript.
Infine, appare evidente che per qualunque tipo di applicazione web che necessiti di un elevato livello di sicurezza è assolutamente indispensabile l’adozione del protocollo HTTPS.
L’HTTPS consiste nella comunicazione tramite il protocollo HTTP all’interno di una connesione criptata dal protocollo crittografico Transport Layer Security (TLS) o dal suo predecessore Secure Socket Layer (SSL).
Il protocollo TLS consente alle applicazioni client/server di comunicare attraverso una rete in modo tale da prevenire il 'tampering' (manomissione) dei dati, la falsificazione e l'intercettazione.
Nell'utilizzo tipico di un browser da parte di utente finale, l'autenticazione TLS è unilaterale: è il solo server ad autenticarsi presso il client (il client, cioè, conosce l'identità del server, ma non viceversa cioè il client rimane anonimo e non autenticato sul server). L'autenticazione del server è molto utile per il software di navigazione e per l'utente. Il browser valida il certificato del server controllando che la firma digitale dei certificati del server sia valida e riconosciuta da una certificate authority conosciuta utilizzando una cifratura a chiave pubblica. Dopo questa autenticazione il browser indica una connessione sicura mostrando solitamente l'icona di un lucchetto.
Il protocollo HTTPS, spesso utilizzato dalle banche o dai negozi online per garantire la massima sicurezza possibile agli utenti, non è tuttavia applicabile ad un progetto di natura prettamente didattica e dimostrativa come questo, in particolare per le incombenze di natura burocratica ed economica che il rilascio del certificato comporta. Per concludere, ritengo quindi di aver adottato le misure minime e sufficienti per garantire la sicurezza del sistema di autenticazione utenti e conservazione delle password, sempre considerando la natura del progetto stesso.