Cari lettori, benvenuti nella seconda parte di questo articolo. Vi ricordo che potete trovare la prima parte del tutorial a questo indirizzo e, inoltre, ribadisco nuovamente il nostro intento: Il tema centrale del tutorial proposto non è tanto la grafica, che potrete ovviamente modificare e personalizzare a vostro piacimento, ma la sicurezza! Vedremo, infatti, i migliori metodi per criptare le password, per resistere ai brute force e per difenderci dalle SQL injection. Anche in questa seconda parte, a fine articolo troverete anche il codice sorgente pronto all'uso!
La pagina functions.php
Tutto il codice seguente farà parte di una pagina chiamata functions.php che conterrà, per l'appunto, tutte le funzioni che andremo poi ad includere e richiamare nelle altre pagine del nostro sito.
1. Inizializzare una sessione sicura in PHP
Prima di tutto, chiariamo uno dei concetti fondamentali sulla sicurezza delle sessioni. L'utilizzo della funzione predefinita "session_start ();" nella parte superiore di ogni pagina che si desidera rendere privata, non è sufficiente ad una sicurezza totale! Il metodo più efficace consiste, invece, nel creare una funzione chiamata "sec_session_start", che inizializzerà una sessione sicura in PHP. Anche in questo caso questa funzione andrà richiamata in ogni pagina che vorrete proteggere. Diamo un'occhiata al codice prima di procedere a commentarlo:
function sec_session_start() { $session_name = 'sec_session_id'; // Diamo un nome alla sessione $secure = false; // Impostate su true se utilizzate l'HTTPS $httponly = true; // Questa variabile impedisce al JS di accedere all'ID di sessione ini_set('session.use_only_cookies', 1); // Forza la sessione ad utilizzare unicamente i cookies $cookieParams = session_get_cookie_params(); // Preleva i parametri correnti dei cookies session_set_cookie_params($cookieParams["lifetime"], $cookieParams["path"], $cookieParams["domain"], $secure, $httponly); session_name($session_name); // Setta il nome di sessione come dalla variabile session_start(); // Inizializzo la sessione session_regenerate_id(true); // Rigenero la sessione sovrascrivendo la precedente }Questa funzione rende impossibile sovrascrivere l'ID della sessione tramite javascript (ad esempio nel caso di attacco XSS). Infatti la "session_regenerate_id ()" è una funzione che rigenera l'id di sessione ad ogni reload della pagina, aiutando a prevenire il dirottamento di sessione.
N.B. Se utilizzi questo codice su una pagina protetta da HTTPS la variabile '$secure' va settata su true.
2. Il login sicuro
È giunto finalmente il momento di vedere il codice della funzione che si occupa del login vero e proprio.
function login($email, $password, $mysqli) { // Utilizare dichiarazioni pre-formate ci protegge dalle SQL injection if ($stmt = $mysqli->prepare("SELECT id, username, password, salt FROM members WHERE email = ? LIMIT 1")) { $stmt->bind_param('s', $email); // Leghiamo il valore dell'email al parametro $stmt->execute(); // Eseguiamo la query $stmt->store_result(); $stmt->bind_result($user_id, $username, $db_password, $salt); // preleviamo il risultato $stmt->fetch(); $password = hash('sha512', $password . $salt); // codifichiamo la password con un codice unico if ($stmt->num_rows == 1) { // Se l'utente esiste // Controlliamo che l'utente non abbia effettuato troppi tentativi di accesso if (checkbrute($user_id, $mysqli) == true) { // Se l'account è bloccato // Inviamo un email all'account per avvertirlo return false; } else { if ($db_password == $password) { // Controlliamo la password // Se la password è corretta $ip_address = $_SERVER['REMOTE_ADDR']; // Preleviamo l'indirizzo IP dell'utente $user_browser = $_SERVER['HTTP_USER_AGENT']; // Risaliamo al browser utilizzato dall'utente // Seguono i codici per la protezione XSS $user_id = preg_replace("/[^0-9]+/", "", $user_id); $_SESSION['user_id'] = $user_id; $username = preg_replace("/[^a-zA-Z0-9_\-]+/", "", $username); $_SESSION['username'] = $username; $_SESSION['login_string'] = hash('sha512', $password . $ip_address . $user_browser); // Login effettuato con successo return true; } else { // Se la password non è corretta // Registriamo i dati del tentativo nella sessione $now = time(); $mysqli->query("INSERT INTO login_attempts (user_id, time) VALUES ('$user_id', '$now')"); return false; } } } else { // Non esistono utenti return false; } } }3. Controllo dei tentativi di brute force
Il brute force, come forse saprete, è una tecnica nella quale il malintenzionato che vuole accedere all'area riservata prova migliaia e migliaia di password generate casualmente o prelevate da un dizionario attraverso un programma automatico. La nostra funzione farà si che, dopo 5 tentativi errati di login, l'utente in questione risulterà bloccato. Vediamo come:
function checkbrute($user_id, $mysqli) { // Preleviamo l'orario corrente $now = time(); // Tutti i tentativi di login vengono immagazzinati per 2 ore dal primo tentativo $valid_attempts = $now - (2 * 60 * 60); if ($stmt = $mysqli->prepare("SELECT time FROM login_attempts WHERE user_id = ? AND time > '$valid_attempts'")) { $stmt->bind_param('i', $user_id); // Eseguiamo la query $stmt->execute(); $stmt->store_result(); // Se ci sono stati più di 5 tentativi if($stmt->num_rows > 5) { return true; } else { return false; } } }Vi ricordo che altri modi per impedire l'uso del brute force sono: inserire un test CAPTCHA oppure ritardare temporaneamente la possibilità di accedere ad ogni tentativo di login fallito.
4. Controllo dello stato di accesso
Il controllo dello stato di accesso avviene selezionando le variabili di sessione "user_id" e "login_string". La variabile "login_string" ha immagazzinato al suo interno gli IP degli utenti, le informazioni del browser e la password criptata.
function login_check($mysqli) { // Controllo che tutte le variabili siano state settate if(isset($_SESSION['user_id'], $_SESSION['username'], $_SESSION['login_string'])) { $user_id = $_SESSION['user_id']; $login_string = $_SESSION['login_string']; $username = $_SESSION['username']; $ip_address = $_SERVER['REMOTE_ADDR']; // Prelevo l'IP dell'utente. $user_browser = $_SERVER['HTTP_USER_AGENT']; // Prelevo i dati del browser if ($stmt = $mysqli->prepare("SELECT password FROM members WHERE id = ? LIMIT 1")) { $stmt->bind_param('i', $user_id); // Leghiamo "$user_id" al parametro $stmt->execute(); // Eseguiamo la query $stmt->store_result(); if($stmt->num_rows == 1) { // Se l'utente esiste $stmt->bind_result($password); // Preleviamo la variabile dal risultato $stmt->fetch(); $login_check = hash('sha512', $password.$ip_address.$user_browser); if($login_check == $login_string) { // Login effettuato con successo return true; } else { // Altrimenti: non loggato return false; } } else { // Altrimenti: non loggato return false; } } else { // Altrimenti: non loggato return false; } } else { // Altrimenti: non loggato return false; } }
0 commenti:
Posta un commento