La metodologia di autenticazione più utilizzata in ambito Web si basa sull'invio delle proprie credenziali - username e password - da browser a server in chiaro, cioè non cifrate.
Se non si utilizza un tunneling SSL (che comporta spese e impostazioni ad hoc), ciò può divenire un problema: in rete locale è estremamente facile utilizzare programmi che intercettino il traffico di rete (sniffing) e conseguentemente riuscire ad ottenere l'accesso al programma Web remoto, utilizzando, appunto, le credenziali altrui così carpite (reply attack).
Questo breve articolo verte su uno stratagemma per evitare l'invio in chiaro della password. Nel nostro script il browser invia lo username; il Web server crea una "sfida" (stringa casuale) e la invia al client; il browser codifica la sfida con la password personale inserita dall'utente ed invia la stringa ottenuta (password_inviata). Si ha, così, che la password personale non viene mai inviata tra client e server (tranne ovviamente una volta, la prima), ma entrambi la conoscono (il server la conserva "gelosamente" nel database, l'utente si spera solo nella sua testa).
Per questo login ho usato una codifica del tipo:
password_inviata = hash (sfida + hash (password_personale) ), con hash scelto = MD5 (SHA1 sarebbe ancora migliore);
Al termine del processo il Web server controlla la validità del login. L'utente è autenticato se:
MD5 (sfida + MD5 (password_personale) ) = password_inviata.
Poiché le tecnologie server-side non conservano la password_personale nel database in chiaro, ma memorizzano il suo hash (qui MD5), si ha che l'utente è autenticato se vale:
MD5 (sfida + password_personale_in_database) = password_inviata.
I preparativi: il database
Il database usato è MySQL, il quale viene pilotato dalla libreria di astrazione ADOdb. La programmazione assistita da questo "wrapper" porta con sé indiscutibili vantaggi ma, poiché non è questa la sede per discuterne, diciamo solo che il suo utilizzo è davvero semplice, quindi, perché non provarlo?
Passi per l'installazione e la messa in strada scarichiamo ADOdb da sourceforge.net e scompattiamone l'archivio che da solo creerà la cartella adodb; poi creiamo (all'interno della cartella adodb) un file di connessione al database (tutti i file con tutta la procedura li trovate anche al link download in alto nell'articolo):
Il file db_conn.php
<?php require_once ("adodb.inc.php"); $HOSTNAME = "localhost"; $DBTYPE = "mysql"; $DATABASE = "nome_database"; $USERNAME = "mysql_username"; $PASSWORD = "mysql_password"; $LOCALE = "It"; ADOLoadCode ($DBTYPE); $db_conn = &ADONewConnection($DBTYPE); $db_conn->PConnect($HOSTNAME, $USERNAME, $PASSWORD, $DATABASE, $LOCALE); ?>
Il file si occupa di creare l'oggetto per la connessione al database ed il suo utilizzo. Le credenziali per l'accesso sono naturalmente da modificare.
I preparativi: la tabella
Qui sotto trovate la struttura della tabella tabella_utente usata e la rispettiva query SQL per la creazione, posto che il database sia già esistente:
Query per la creazione della tabella
CREATE TABLE `tabella_utente` ( `id_utente` int(10) unsigned NOT NULL auto_increment, `cognome` varchar(255) default NULL, `username` varchar(255) default NULL, `password` varchar(255) default NULL, `sfida_corrente` varchar(255) default NULL, PRIMARY KEY (`id_utente`) ) ENGINE=MyISAM DEFAULT CHARSET=utf8;
Per ogni utente, in fase di registrazione, inseriremo qui cognome, username e password. La password, come sempre, non è in chiaro, ma in sua vece memorizzeremo l'hash MD5: password_database = MD5(password_utente).
La seguente voce memorizzata sul database ne è un esempio. L'hash nell'immagine è la firma della password my|pass|2007|02|02
; l'ultima colonna servirà alla procedura di login.
Disposizione dei file
La struttura del programma è la seguente (copiare dove indicato i file qui inclusi). Poniamo di inserire gli script di login nella sottocartella login della cartella www (htdocs).
login/index.php login/main.php login/login_failed.php login/CLASSES/secure_login_class.php login/CLASSES/adodb/* login/JAVASCRIPT/ajax_login_handler.js login/JAVASCRIPT/md5.js
La funzione JavaScript MD5 contenuta nel file md5.js è stata scaricata dal sito www.webtoolkit.info, ma ne esiste ben più d'una sulla Rete.
Lo Script
Il file index.php
<?php // Inclusioni require_once ('CLASSES/adodb/db_conn.php'); require_once ('CLASSES/secure_login_class.php'); // ************* server // Inserito lo username lato client, il browser lo comunica allo script // PHP "server" (tramite comunicazione ajax asincrona), il quale (qui) // crea, salva ed invia una sfida al client. if (isset($_GET['__user'])) { if (trim($_POST['user'])!="") { // Ritorna la sfida al client. $login = new classe_login($db_conn); echo $login->invia_sfida($_POST['user']); } exit; } // Al submit (finale) del form, il server verifica le credenziali di accesso. if (isset($_GET['__submit'])) { if (trim($_POST['user'])!="" && trim($_POST['pwd'])!="" && trim($_POST['copia_sfida'])!="") { $login = new classe_login($db_conn); if ($dati = $login->verifica_login($_POST['user'],$_POST['pwd'],$_POST['copia_sfida'])) { // Utente valido: inserisco i dati contenuti nel db nella sessione. session_regenerate_id(); session_start(); $_SESSION['userid'] = $dati['userid']; $_SESSION['cognome'] = $dati['cognome']; header('Location: main.php'); } else header('Location: login_failed.php'); exit; } //altrimenti ri-visualizza il form di login. } // ************* client ?> <html> <head> <title>ajax login</title> <meta http-equiv="Content-Type" content="text/html; charset=utf-8"> <script type="text/javascript" src="JAVASCRIPT/ajax_login_handler.js"></script> <script type="text/javascript" src="JAVASCRIPT/md5.js"></script> </head> <body OnLoad="document.login.user.focus();"> <form name="login" method="post" action="index.php?__submit" OnSubmit="codifica_password()"> <table> <tr> <td>Username: </td> <td><input type="text" name="user" OnChange="comunicazione_ajax('index.php?__user',this.name,this.value)" OnKeyUp="document.login.pwd.disabled = false;"></td> </tr> <tr> <td>Password: </td> <td><input type="password" name="pwd" disabled></td> <!-- il campo password verrà abilitato non appena viene inserito qualcosa nel campo user --> </tr> <tr> <td colspan="2" align="center"> <input type="submit" name="submit" value="Entra" disabled> <!-- il tasto di submit verrà abilitato non appena il server avrà terminato di ritornare la sfida --> <input type="hidden" name="copia_sfida"> </td> </tr> </table> </form> </body> </html>
Lo script, dal quale è stato eliminato ogni "orpello" grafico così da rendere più comprensibile l'esposizione, è suddiviso in tre parti.
Alla prima richiesta di index.php da parte del browser (prima connessione al programma Web), non essendo soddisfatte le condizioni ad inizio e metà script, viene ad esso inviato dal Web server il codice HTML tra i tag <html>
e </html>
senza che nulla venga eseguito. Nel codice HTML è presente un form in cui è possibile inserire lo username, la password e cliccare sul tasto Entra, che si occuperà dell'effettivo invio (finale) dei dati al server (in modalità sincrona, come d'abitudine).
Prima di questo, però, non appena l'utente ha completato l'inserimento dello username (ciò viene definito dallo handler JavaScript OnChange
), attraverso la metodologia di comunicazione asincrona AJAX, il browser invia al server (index.php?__user
) lo username stesso.
La funzione che si occupa di questo invio (e ricezione) è comunicazione_ajax()
di ajax_login_handler.js.
Lato server viene eseguita la prima parte dello script che si occupa di creare una sfida (stringa casuale), salvarla su database (per riconoscere nel futuro l'utente al quale corrisponde) ed inviarla al client, ovvero nella fattispecie all'oggetto XHR, che si occupa, a sua volta, tramite handler_risposta_server()
, di salvare la sfida nel campo nascosto HTML copia_sfida
.
La classe PHP classe_login
è contenuta in secure_login_class.php.
Siamo ora al punto in cui l'utente ha inserito lo username che è stato inviato al server in maniera asincrona. Il server ha inviato di ritorno una sfida al client, memorizzata in un campo nascosto. Alla pressione del pulsante di invio viene operata la codifica su detta (tramite codifica_password()
di ajax_login_handler.js) ed inviata al server (index.php?__submit
) in vece della password inserita manualmente dall'utente.
Nel caso di login valido, viene creata, lato server, la sessione con i dati di cui nel codice e si viene rindirizzati all'area autenticata del programma Web. In caso contrario, si viene gentilmente accompagnati all'uscita.
Conclusioni: da cosa non protegge lo script
Lo script non protegge dalla possibilità di sniffing sullo scambio iniziale della password (parliamo però di tutt'altro argomento) e dalle passowrd deboli: dati sfida e password_inviata sniffati - sono inviati in chiaro - il cracker può comunque tentare un brute forcing, senza vincoli di pause forzate imposti invece dall'applicazione. Eventuali collisioni "giocano" però a favore degli utenti legittimi.
I file inclusi nell'archivio zip scaricabile dal link download sono i seguenti:
index.php
main.php
login_failed.php
ajax_login_handler.js
secure_login_class.php