Posts RSS
gen/1219

Creare siti in PHP – (28) – La sicurezza in PHP: XSS e SQL Injection

scritto da Federico | categoria: scripting, web design

La sicurezza in PHP: XSS e SQL Injection
Iniziamo oggi una breve panoramica su due dei problemi di sicurezza più comuni degli script PHP.

XSS: Cross-site scripting

Partiamo dal cosiddetto “Cross-site scripting”, noto anche come XSS: si tratta di un problema, molto diffuso, che riguarda la sicurezza dei form e in particolare i form che non applicano controlli sui campi. Il malintenzionato può, tramite il form, far eseguire un codice JavaScript che magari rimanda a un suo sito dove è contenuto uno script dannoso. A proposito: ricordo a tutti quelli che leggono (visti i numerosi accessi al sito) che alcune delle cose che sto per illustrare, se messe in pratica, costituiscono un reato penalmente perseguibile. Ve ne parlo solo affinché vi possiate difendere.

Tornando al nostro XSS, prendiamo un semplicissimo form per inserire un messaggio:

<form action="xss.php" method="get">
Nome:<br />
<input type="text" name="nome" /><br />
Messaggio: <br />
<textarea rows="10" cols="20" name="messaggio"></textarea><br />
<input type="submit" name="invia" value="Invia!" /><br />
</form>

E supponiamo che il codice della pagina xss.php sia strutturato in questo modo:

<?php
echo "Nome: {$_GET['nome']}";
echo "<br /><br />Messaggio: <br /><br />";
echo stripslashes($_GET['messaggio']);
?>

Bene, questo è un codice del tutto insicuro perché non ci sono controlli di sicurezza. Nel campo “messaggio” l’hacker può facilmente inserire un codice JavaScript di questo tipo:

<script language="text/javascript">document.location.href="http://www.web-magazine.it";</script>

Dove ovviamente al posto di web-magazine.it c’è un sito malvagio. Attraverso la stampa con echo, il codice viene inserito nella pagina xss.php e quindi eseguito. E ovviamente, date le tante cose che si possono fare con JavaScript, il reindirizzamento non è che una possibilità: il malintenzionato può infatti anche installare script dannosi direttamente all’interno del sito. E questo tipo di attacco funziona sia che il nostro form abbia metodo get, sia che abbia metodo post. Tipicamente, gli script XSS vengono utilizzati per rubare cookie ai malcapitati.

Come difendersi dal cross-site scripting

Difendersi da questo tipo di attacchi è veramente molto semplice: bisogna solo tenerlo a mente! Basterà fare in modo che i caratteri riservati del linguaggio HTML vengano sostituiti con le relative entità (per esempio la parentesi uncinata aperta con &lt;) o che le etichette HTML vengano del tutto cancellate.

Per fare questo abbiamo tre funzioni: htmlspecialchars, htmlentities e strip_tags. Funzionano tutte allo stesso modo: ricevono come parametro la stringa per la quale dobbiamo sostituire i caratteri. Ecco quindi qual è il codice corretto e sicuro da utilizzare nella pagina xss.php:

<?php
echo "<br /><br />Messaggio: <br /><br />";
echo htmlspecialchars(stripslashes($_GET['messaggio']));
?>

Oppure

echo htmlentities(stripslashes($_GET['messaggio']));

O ancora

echo strip_tags(stripslashes($_GET['messaggio']));

htmlspecialchars, htmlentities e strip_tags

Ci sono ovviamente alcune differenze tra le tre funzioni. La differenza tra htmlspecialchars e htmlentities consiste nel fatto che la prima converte in entità solo i caratteri riservati di HTML, mentre la seconda converte anche i caratteri speciali (per esempio, se dovessimo scrivere in tedesco, la o con la dieresi – ö – sarebbe convertita in &ouml;). La funzione strip_tags invece elimina del tutto le etichette HTML. In ogni caso, il codice immesso dal malintenzionato verrebbe stampato sullo schermo e non eseguito (anzi, nel caso di strip_tags le tag verrebbero del tutto eliminate). E ricordate sempre: uno dei migliori metodi per difendersi dagli attacchi è filtrare tutto ciò che arriva in input sul nostro sito. In questo caso abbiamo visto come filtrare attraverso le tre funzioni di cui abbiamo parlato: potete trovare le documentazioni ufficiali delle funzioni a queste pagine: http://www.php.net/manual/en/function.htmlspecialchars.php (htmlspecialchars), http://php.net/manual/en/function.htmlentities.php (htmlentities), http://it.php.net/manual/en/function.strip-tags.php (strip_tags).

SQL Injection

Il secondo problema di vulnerabilità che vediamo oggi è la cosiddetta SQL Injection (iniezione di SQL). Anche in questo caso l’attacco prende di mira form scritti male per eseguire un codice SQL malevolo, e adesso vedremo nel dettaglio in cosa consiste questa pratica e perché è tanto pericolosa. Prendiamo per esempio questo semplice form per connettersi a un sito:

<form action="login.php?step=2" method="post">
Nickname:<br />
<input type="text" name="nickname" /><br />
Password: <br />
<input type="password" name="psw" /><br />
<input type="submit" name="invia" value="Invia!" /><br />
</form>

In questo caso il nostro form, invece di rimandare a una nuova pagina, rimanda alla stessa ma con l’aggiunta della query string ?step=2, in modo da scrivere tutto nella stessa pagina. L’azione del form andrà quindi scritta in un blocco if di questo tipo:

if (isset($_GET['step']) && $_GET['step'] == 2) {
// azione
}
else {
}

Ipotizziamo anche che il gestore del sito o del blog sia stato così sprovveduto da aver fatto in modo che le password vengano inserite in chiaro nel database (quindi senza criptarle attraverso il form di registrazione). Ora prendiamo il pessimo script che esegue il controllo sui dati immessi dall’utente:

$query = "SELECT * FROM autori WHERE Nickname = '{$_POST['nickname']}' AND Password = '{$_POST['psw']}'";
$risultato = mysql_query($query);
if (mysql_num_rows($risultato) == '1') {
echo "Benvenuto, utente autenticato";
// codice che mostra l'eventuale pagina personale dell'utente
}
else {
echo "Spiacente, password errata";
}

Chiariamo subito la funzione nuova, mysql_num_rows: restituisce il numero dei record trovati dalla query (nel nostro caso uno, anche perché si suppone che gli username siano univoci) e riceve come parametro il risultato della query. Qua si trova la documentazione ufficiale della funzione: http://php.net/manual/en/function.mysql-num-rows.php. Pertanto se la funzione dà come risultato 1 (e quindi trova il record), l’utente sarà loggato, in caso contrario ci sarà un messaggio che informerà l’utente di aver inserito una password errata.

Questo codice è estremamente vulnerabile, soprattutto nel caso in cui l’impostazione magic quotes gpc di PHP sia disattivata: in locale potete controllarlo su WAMP cliccando sull’icona nella barra delle applicazioni, quindi andare alla voce PHP e ancora andare su Impostazioni PHP. Se la voce “magic quotes gpc” è spuntata significa che è attiva e avete maggiori protezioni contro le SQL Injection. Ci sono però tanti provider che non attivano le magic quotes sui loro server. Ma cosa sono le magic quotes? Si tratta di un processo che aggiunge in automatico i caratteri di escape alle stringhe, ma dobbiamo sapere che a partire dalla versione 5.3.0 di PHP, magic quotes è deprecato e nella versione 6 addirittura non c’è più.

Vediamo quindi cosa può fare il malintenzionato. Nella casella nickname può inserire il nome “Federico”, e fin qui niente di strano. Ma nella casella password può tranquillamente inserire questa stringa:

' OR Nickname='Federico

Così facendo, il nostro codice eseguirebbe questa query:

$query = "SELECT * FROM autori WHERE Nickname = 'Federico' AND Password =
'' OR Nickname='Federico'";

La clausola OR inserita dal malintenzionato vanificherà il controllo sulla password: questo perché la query strutturata in tal modo dice di selezionare tutti i record della tabella autori il cui nickname sia Federico e la cui password sia vuota oppure, in alternativa a quest’ultima clausola, il cui nickname sia Federico. Dal momento che la seconda clausola è soddisfatta il malintenzionato riuscirà a entrare agevolmente al posto di Federico all’interno del sito, spacciandosi per lui e magari rubando i suoi dati… o insomma facendo tutto ciò che di cattivo si può fare entrando nel profilo di un’altra persona.

Criptiamo la password con md5

La prima operazione da fare è criptare la password attraverso la funzione md5: nel database non dovrà essere inserita in chiaro ma andrà fatta passare attraverso questa funzione (in un’ipotetica query con comando INSERT INTO metteremo tra i VALUES ‘md5($_POST['password']‘). MD5 è un algoritmo crittografico di hashing che fu realizzato nel 1991 da Ronald Linn Rivest, un crittografo statunitense: l’algoritmo trasforma una stringa in un’altra stringa a 128 bit, e non è possibile risalire alla stringa originale (se non andando… a tentativi). La funzione md5 di PHP riceve come parametro semplicemente la stringa da criptare e la documentazione ufficiale si trova qui: http://it.php.net/manual/en/function.md5.php.

Questo è solo uno dei tanti modi per criptare una password: parliamo di questo perché è il più semplice da utilizzare, ma c’è chi usa anche sistemi più sofisticati. Il nostro codice pertanto dovrà presentarsi in questa forma:

$query = "SELECT * FROM autori WHERE Nickname = '{$_POST['nickname']}' AND Password = '". md5($_POST['psw']). "'";

Dove i punti che separano md5($_POST['psw']) dal resto del codice sono un modo per concatenare valori all’interno di una variabile (le virgole non funzionano).

In questo caso ci salviamo dal malintenzionato che immette i dati di cui sopra, ma siamo ancora vulnerabili. Questo perché l’hacker potrebbe scrivere nella casella del nickname questa stringa cattivissima:

Federico' --

E magari può anche lasciare in bianco il campo della password. In tal modo la query diventerebbe questa:

$query = "SELECT * FROM autori WHERE Nickname = 'Federico' -- AND Password =
''";

I due trattini nel linguaggio SQL indicano i commenti: pertanto tutto ciò che viene dopo i due trattini sarà ignorato. Capite quindi la pericolosità della cosa malgrado il controllo tramite algoritmo md5. Sarà un giochetto per il malintenzionato riuscire a entrare nel sito e magari carpire tutta la lista degli utenti registrati o a far di peggio: pensate se potesse entrare con l’account dell’amministratore.

Come difendersi dalla SQL injection

Qual è pertanto il modo più sicuro per difendersi da questo tipo di attacco? Non dovremo far altro che aggiungere una funzione per controllare i dati immessi dall’utente (ricordate… filtrare sempre tutto), e si tratta della funzione mysql_real_escape_string, che inserisce i giusti caratteri di escape prima dei caratteri “pericolosi”. Riceve come parametro semplicemente la stringa a cui aggiungere i parametri di escape. Quindi la nostra query dovrà assumere questa forma per evitare attacchi:

$query = "SELECT * FROM autori WHERE Nickname = '". mysql_real_escape_string($_POST['nickname']) ."' AND Password = '". mysql_real_escape_string($_POST['psw']). "'";

Riusciremo così a rendere inoffensivo l’attacco del malintenzionato, perché l’ultima stringa che abbiamo visto risulterà invece in questo modo:

$query = "SELECT * FROM autori WHERE Nickname = 'Federico\' -- AND Password = ''";

Questa funzione va utilizzata solo nel caso in cui magic quotes gpc sia disattivo. Per essere però del tutto sicuri utilizziamo la funzione get_magic_quotes_gpc, che controlla la configurazione delle magic quotes (non riceve alcunché come parametro), e inseriamola in un blocco if:

if(get_magic_quotes_gpc()) {
$query = "SELECT * FROM autori WHERE Nickname = '". $_POST['nickname'] ."' AND Password = '".
$_POST['psw']. "'";
}
else {
$query = "SELECT * FROM autori WHERE Nickname = '". mysql_real_escape_string($_POST['nickname']) ."'
AND Password = '". mysql_real_escape_string($_POST['psw']). "'";
}

Controlliamo con il ciclo che il processo magic quotes gpc sia attivo, e in caso affermativo creiamo la nostra query senza la funzione mysql_real_escape_string, e in caso contrario ovviamente la aggiungiamo. Trovate la documentazione ufficiale di queste due funzioni qui http://it.php.net/manual/en/function.mysql-real-escape-string.php (mysql_real_escape_string) e qui http://php.net/manual/en/function.get-magic-quotes-gpc.php (get_magic_quotes_gpc).

Vi aspetto come sempre alla prossima puntata!

Puoi trovare interessante anche:

Esprimi un giudizio sul post!

mmm... poco interessanteMi piace! (Nessun voto per ora)
Loading ... Loading ...
Tags: , , , , , | letture: 456
torna su

Commenta l'articolo