OBIETTIVI DELLA LEZIONE
Rivedere quanto appreso nelle scorse lezioni alla luce di una applicazione WEB leggermente più complessa; inoltre approfondire alcuni dettagli utili per sviluppare gli elaborati.
Attraverso questo lungo esempio (essessione3.php), ripassiamo le cose imparate nelle scorse lezioni e vediamo come, mettendo insieme le cose imparate, sia possibile costruire un applicazione che, seppure didattica, sia in grado di grarantire un grado di sicurezza minimale.
Il database contiene un solo utente sassi e la password è semplicemente sassi.
Un file iniziale di funzioni di servizio: miefunzioni.inc
<?php
function verifica_SSL()
{
if( $_SERVER['HTTPS'] !== 'on' )
{
# La connessione non è su socket SSL; invita il browser del client a
# ricaricare la stessa pagina tramite HTTPS
header("Location: https://{$_SERVER['SERVER_NAME']}{$_SERVER['PHP_SELF']}");
exit;
}
# Imposta opzione "cookie_secure": il cookie di sessione verrà
# restituito dal client solo su SSL
ini_set("session.cookie_secure", TRUE);
}
function verifica_utente()
{
# Controlla che l'IP dell'utente non sia cambiato da quando
# è stato identificato
if( isset($_SESSION['utente']) and
($_SESSION['IP_utente']!==$_SERVER['REMOTE_ADDR']) )
$_SESSION['utente']=FALSE;
# Identifica l'utente se e' necessario
if( !isset($_SESSION['utente']) or ($_SESSION['utente']===FALSE) )
{
header("Location: http://" . $_SERVER['HTTP_HOST'] .
dirname($_SERVER['PHP_SELF']) . "/identifica1.php");
exit;
}
}
?>
Lo script che identifica l'utente: identifica1.php. Utilizza la tabella utenti creata con il comando SQL:
CREATE TABLE `tab_utenti` ( `id` INT UNSIGNED NOT NULL AUTO_INCREMENT , `utente` VARCHAR( 50 ) NOT NULL , `password` VARCHAR( 32 ) NOT NULL , PRIMARY KEY ( `id` ) );
<?php
require('miefunzioni.inc');
verifica_SSL();
session_start();
# Identificazione Utente
if( !isset($_POST['utente']) or !isset($_POST['password']) )
{
?>
<html>
<head>
<title>Banca PincoPallino</title>
</head>
<body>
<h3>Benvenuto alla Banca PincoPallino</h3>
Per cortesia inserire il nome dell'utente e la password
<form action="<?php print $_SERVER['PHP_SELF']; ?>" method="POST">
<table>
<tr>
<td>Nome Utente:</td>
<td><input type="text" name="utente"></td>
</tr>
<tr>
<td>Password:</td>
<td><input type="password" name="password"></td>
</tr>
<tr>
<td></td>
<td><input type="submit" value="Identificati"></td>
</tr>
</table>
</form>
</body>
</html>
<?php
}
else
{
$ptr_mysqls=mysql_connect("localhost","root","")
or die("Impossibile connettersi al server MySQL.\n");
mysql_select_db("dbprova1", $ptr_mysqls)
or die("Impossibile aprire il database.\n");
$md5password=md5($_POST['password']);
$query_visualizzazione="SELECT * FROM tab_utenti" .
" WHERE utente='{$_POST['utente']}' AND password='$md5password'";
$ptr_risultato_query=mysql_query($query_visualizzazione, $ptr_mysqls);
$numero_records=mysql_affected_rows($ptr_mysqls);
mysql_close($ptr_mysqls);
if($numero_records == 1)
{
# Utente identificato
$_SESSION['utente']=$_POST['utente'];
$_SESSION['IP_utente']=$_SERVER['REMOTE_ADDR'];
header("Location: http://" . $_SERVER['HTTP_HOST'] .
dirname($_SERVER['PHP_SELF']) . "/essessione3.php");
exit;
}
?>
<html>
<head>
<title>Banca PincoPallino</title>
</head>
<body>
<h3>Attenzione, Nome Utente o Password non corretti.</h3>
</body>
</html>
<?php
}
?>
Il file principale: essessione3.php
<?php
require('miefunzioni.inc');
verifica_SSL();
# Se si arriva a questa riga la connessione sta avvenendo su canale criptato;
# Inizializza o ripristina la sessione
session_start();
verifica_utente();
# Se si arriva a questa riga L'utente e' identificato e non ha cambiato
# IP di connessione
?>
<html>
<head>
<title>Banca PincoPallino</title>
</head>
<body>
<h3>Banca PincoPallino</h3>
Salve <?php print $_SESSION['utente']; ?>,
benvenuto al sito della Banca PincoPallino.<br />
Effettua le tue operazioni con la tranquillità della sicurezza!<br />
<br />
<br />
[... operazioni varie ...]
<br />
<br />
<br />
Cambia la tua password:
<form action="modifica1.php" method="post">
<input type="password" name="nuovapassword1">
<input type="password" name="nuovapassword2">
<input type="submit" value="Conferma">
</form>
<br />
<a href="chiudi1.php">Chiudi la sessione</a>.
</body>
</html>
lo script che modifica la password: modifica1.php
<?php
require('miefunzioni.inc');
verifica_SSL();
session_start();
verifica_utente();
# Cambia la password
$numero_records=0;
if( isset($_POST['nuovapassword1']) and isset($_POST['nuovapassword2']) and
($_POST['nuovapassword1']===$_POST['nuovapassword2']) )
{
$ptr_mysqls=mysql_connect("localhost","root","")
or die("Impossibile connettersi al server MySQL.\n");
mysql_select_db("dbprova1", $ptr_mysqls)
or die("Impossibile aprire il database.\n");
# NON memorizziamo la password ma la sua trasformata md5
$md5password=md5($_POST['nuovapassword1']);
$query_modifica="UPDATE tab_utenti SET password='$md5password'" .
" WHERE utente='{$_SESSION['utente']}'";
$ptr_risultato_query=mysql_query($query_modifica, $ptr_mysqls);
$numero_records=mysql_affected_rows($ptr_mysqls);
mysql_close($ptr_mysqls);
}
?>
<html>
<head>
<title>Richiesta Utente</title>
</head>
<body>
<h3><?php
if($numero_records === 1)
print "Operazione conclusa correttamente.";
else
print "Attenzione, impossibile completare l'operazione richiesta.";
?></h3>
</body>
</html>
Lo script di chiusura chiudi1.php
<?php
require('miefunzioni.inc');
verifica_SSL();
session_start();
verifica_utente();
# Disalloca tutte le variabili della sessione.
session_unset();
# Distrugge la sessione.
session_destroy();
?>
<html>
<head>
<title>Banca PincoPallino</title>
</head>
<body>
<h3>Arrivederci, grazie di aver utilizzato i nostri servizi online.</h3>
</body>
</html>
Durante la seconda parte del corso abbiamo visto come sia possibile utilizzare lato client un form HTML per inviare dati inseriti dall'utente ad una CGI lato server.
Nei forms che abbiamo preparato, l'utente è stato libero di inserire ciò che più gli aggradava. Così quando chiedevamo una data, se l'utente avesse immesso una parola i nostri scripts PHP non avrebbero protestato, ma al più avrebbero funzionato in modo scorretto. E se siamo disposti a pagare questo prezzo in termini di utilizzabilità del sito, può anche andare bene così.
Ciò che non abbiamo mai tenuto in considerazione, è invece il caso in cui un malintenzionato volesse deliberatamente sfruttare il fatto che non verifichiamo gli input per manipolare o accedere al nostro sito.
Sono detti command injection flaws (falle per l'iniezione di comando) tutti quegli input (non controllati) che vengono passati come argomenti a programmi esterni o come parte di query SQL a databases. Vediamone un esempio. Torniamo all'esempio visto in precedenza. Nel file identifica1.php sostituiamo la riga
$query_visualizzazione="SELECT * FROM tab_utenti" . " WHERE utente='{$_POST['utente']}' AND password='$md5password'";
con la seguente (differenza evidenziata in giallo):
$query_visualizzazione="SELECT * FROM tab_utenti" . " WHERE utente='{$_POST['utente']}' AND password='{$_POST['password']}'";
Sembra che la differenza non sia sostanziale. Proviamo ora ad utilizzare il nuovo script essessione4.php con
utente: ' OR ''=' password: ' OR ''='
Il sito ci riconosce come utente registrato anche se l'unico utente registrato è sassi. L'attaccante è riuscito quindi con una SQL injection (iniezione di codice SQL) a forzare il meccanismo di autenticazione. Come ha fatto? Se sostituiamo gli input utente nella stringa della query verifichiamo facilmente che l'utente è riuscito a modificare la condizione logica su cui si basava la nostra query.
$query_visualizzazione="SELECT * FROM tab_utenti" . " WHERE utente='' OR ''='' AND password='' OR ''=''";
L'operazione logica ''='' in SQL è una tautologia (è sempre vera). Problemi come questi sono spesso frequenti nelle applicazioni WEB. Per evitarlo alla radice bisogna porre notevole attenzione e aver studiato bene il PHP (e l'SQL). Molti problemi di vulnerabilità nascono dal fatto che alcuni programmatori utilizzano il taglia/incolla di codice scritto da altri e che non hanno capito a fondo. Una seconda utile abitudine è quella di "disinfettare" (sanitize) l'input dell'utente per evitare che possa inserire caratteri o sequenze problematiche. In questo modo, l'input utente PRIMA di essere utilizzato viene trattato in modo da eliminare tutti i caratteri che non sono ammessi in quel contesto. L'operazione va fatta lato server! Lato client (ad esempio in JavaScript) non è affatto efficace a prevenire questo tipo di problemi (l'utente può sempre evitare i controlli connettendosi al server in telnet sulla porta 80). Esistono numerose librerie di funzioni già pronte allo scopo; un esempio di riferimento è la collezione OWASP PHP Filters, che fa parte di un più ambizioso progetto di validazione degli input, l' OWASP Validation Project.
Un elenco delle 5 tecniche di attacco più utilizzate nel 2005 contro una applicazione PHP è riportato nello studio PHP Top 5. Un buon programmatore PHP deve comprendere a fondo questi problemi per evitare che gli attacchi al sito abbiano efficacia. SQL injection è stata una delle 5 tecniche più utilizzate nel 2005.
Per la preparazione, potete scaricare un file che contiene 20 domande di livello simile a quelle che troverete all'esame. Le risposte sono contenute in questo secondo file.
Esercizio Facoltativo
Dato il database dell'esercizio 2 della scorsa lezione, preparare lo script sommaspese.php che chieda all'utente due date e calcoli in uscita la somma del campo "importo" per le spese effettuate nell'intervallo specificato.
©2005 Roberto Sassi