guida alla sicurezza di php

 

In questa guida alla sicurezza di php elencherò le principali best pratice in tema di sicurezza nella programmazione in php.

Php è uno dei più diffusi linguaggi di programmazione orientati al web e proprio a causa della sua diffusione è anche uno dei linguaggi più soggetti ad errori di programmazione e in ultima analisi a vulnerabilità.

Cercherò di fornire una lista delle principali vulnerabilità generate da errori di programmazione o errate configurazioni.

 

Validazione

Il codice php richiamato da funzioni come include(), include_once(), require() e require_once() non dovrebbe mai essere passato come argomento in una richiesta GET o POST senza essere validato, perchè questo potrebbe tradursi in una grossa falla nella sicurezza di php.

Nell’esempio seguente abbiamo una pagina index.php con il seguente codice:

<?php 
  include($_GET['pagina']); 
?>

ed una pagina di test chiamata home.php:

<?php
 echo "Questa è la homepage"; 
?>

possiamo richiamare la pagina home.php con il seguente link:

www.site.com/index.php?pagina=home.php

ma se invece di home.php passiamo un altro file come argomento pagina, ad esempio .htaccess:

www.site.com/index.php?pagina=.htaccess

potremo visualizzare un file normalmente non accessibile, oltre che qualsiasi altro file nella directory corrente ed in altre directory.

Anche se modifichiamo il file index.php in modo da includere solo file con estensione php in questo modo:

<?php 
  include($_GET['pagina'].".php"]); 
?>

 è comunque possibile richiamare il file htaccess con il byte null %00:

www.site.com/index.php?pagina=.htaccess%00

per maggiori informazioni sulla Null Byte Injection consulta questo link.

Per ovviare a questo problema è sufficiente controllare i parametri passati ed accertarsi che appartengano ad una white list, ad esempio mediante uno switch:

switch($_GET['pagina']){
    case "home": $pagina="home.php"; break;
    case "azienda": $pagina="azienda.php"; break;
    case "chi_siamo": $pagina="chi_siamo.php"; break;
    default: $pagina="home.php";
}
include($pagina);

In questo modo se vengono passati argomenti non compresi nella white list varrà restituita la pagina di default.

 

Cross site Request Forgery

Questo tipo di vulnerabilità consiste nel passare comandi al server usando i privilegi di un altro utente ignaro con privilegi maggiori. Questo diventa possibile quando il sito non convalida le richieste POST e GET e non si accerta della loro provenienza.

Facciamo un esempio.

Il seguente script php serve a cancellare gli utenti passando il loro ID come parametro, ma solo l’amministratore può eseguirlo:

www.sito.com/CancellaUtente.php?ID=3

poniamo che un altro utente inserisca l’URL precedente in una pagina HTML che prevede il caricamento di un’immagine:

<html>
 <body>
  <img src='http://www.sito.com/CancellaUtente.php?id=3'/>
 </body>
</html>

e riesca a farla visualizzare dall’amministratore con uno stratagemma. Il browser, che è predisposto per caricare automaticamente immagini, si collegherà all’indirizzo ottenendo come risultato la cancellazione  dell’utente con ID=3.

Per risolvere questa vulnerabilità è sufficiente aggiungere al parametro ID un ulteriore parametro token che servirà a validare l’utente che effettua la richiesta, come di seguito:

www.sito.com/CancellaUtente.php?ID=3&token=Xksj34hd4758ge645jf96lhfutk4750u3

In questo modo anche se l’utente malintenzionato riuscisse a far visualizzare la pagina ad un amministratore, il token generato non sarebbe quello atteso e la richiesta verrebbe rigettata.

La soluzione migliore sarebbe di generare un nuovo token per ogni pagina e per ogni sessione, anche se di solito è sufficiente solo il token di sessione.

 

Validazione delle mail

Anche gli indirizzi mail dovrebbero essere correttamente verificati. In PHP esiste un metodo efficace per farlo, la funzione filter_var:

filter_var('bob@example.com', FILTER_VALIDATE_EMAIL);

 

Validazione delle query al database

Nella gran parte dei siti web che generano dinamicamente i contenuti, questi ultimi vengono memorizzati in un database e richiamati mediante query in php.

Vediamo un esempio. La seguente form:

<form method="POST" action="utenti.php">
  Username:<br>
  <input type="text" name="username"><br>
  Password:<br>
  <input type="password" name="password">
  <input type="submit" value="Submit">
</form>

chiede di inserire username e password e li invia ad utenti.php con una richiesta POST. Questo è il codice di utenti.php:

$risult = mysql_query("select * from utenti where username='{$_POST['username']}' and 
password='{$_POST['password']}'");

se come username e password passiamo la stringa ‘ OR ‘0’=’0 otteniamo l’esecuzione della seguente query:

$risult = mysql_query("select * from utenti where username='' OR '0'='0' and 
password='' OR '0'='0'");

che risulta sempre vera. La richiesta riformulata in  questo modo restituisce il primo record presente sul database che quasi sempre corrisponde a quello amministrativo, permettendo così l’accesso con privilegi di amministratore senza conoscere le credenziali.

Questo attacco, peraltro molto frequente, è noto come SQL injection, per un approfondimento si consiglia il seguente link.

Il modo migliore per contrastare le SQL injection è di aggiornare le estensioni MySQL alle nuove PDO_MySQL ed a MySQLi, ed utilizzare le prepared statement, nelle quali i dati vengono passati dall’utente come parametri alla  query nel seguente modo:

$stmt = $pdo->prepare('SELECT * FROM utenti WHERE username= :name AND password = :password');
$stmt->execute(array(':username' => $_POST["username"],':password'=>$_POST["password"]));
foreach ($stmt as $row) { 
// fai qualcosa con $row 
}

oppure usando MySQLi:

$stmt = $dbConnection->prepare('SELECT * FROM utenti WHERE username = ? AND password = ?');
$stmt->bind_param('ss', $_POST["username"], $_POST["password"]);
$stmt->execute();

$result = $stmt->get_result();
while ($row = $result->fetch_assoc()) {
    // fai qualcosa con $row
}

In realtà però a certe condizioni è possibile bypassare anche le prepared statement, così un metodo davvero efficace per validare i dati in input è il seguente:

  1. per gli interi si può usare
    sprintf("SELECT * FROM utenti WHERE ID = %u", $ID);
  2. per le stringhe usare la conversione in esadecimale
    SELECT username FROM users WHERE username = UNHEX('726f6f74')

Per finire gli utenti del database dovrebbero avere solo il minimo livello di permessi necessari. Per esempio, se il codice richiede solo i comandi select, update, delete e insert, gli utenti dovrebbero possedere solo i permessi per le medesime operazioni.

 

Cross Site Scripting

L’attacco Cross site Scripting (XSS) si basa sull’iniezione di codice javascript all’interno di una pagina HTML in modo che il browser esegua istruzioni decise dall’attaccante, come redirect o download di file.

Ecco un esempio molto semplice. Poniamo di avere il seguente script dimostrativo XSS.php:

<html>
 <body>
   <?php
    echo "<p>Tu sei l'utente ". $_GET["utente"]."</p>";
   ?>
 </body>
<html>

Se forniamo il nome di un utente, ad esempio Mario:

www.sito.com/XSS.php?utente=Mario

Lo script restituirà correttamente:

Tu sei l'utente Mario

Ma proviamo invece ad inserire un codice javascript:

www.sito.com/XSS.php?utente=<alert>("hacked")</script>

Il risultato sarà la visualizzazione di una finestra pop-up con la scritta “hacked”.

Alcuni browser come Google Chrome presentano un sistema di filtraggio del Cross Site Scripting chiamato XSS Auditor, il quale può comunque essere aggirato:

www.sito.com/XSS.php?a=<script>void('&b=');alert("hacked");</script>

come suggerito da blog.securitee.org/?p=37.

Per prevenire l’attacco XSS è necessario controllare e validare tutti i dati forniti dall’utente che vengono visualizzati in una pagina web. Un metodo è quello di utilizzare la funzione strip_tags():

<html>
 <body>
   <?php
    echo "<p>Tu sei l'utente ". strip_tags($_GET["utente"])."</p>";
   ?>
 </body>
<html>

oppure htmlentities():

<html>
 <body>
   <?php
    echo "<p>Tu sei l'utente ". htmlentities($_GET["utente"], ENT_QUOTES)."</p>";
   ?>
 </body>
<html>

 

Iniezione diretta di codice php

Alcune funzioni di php come eval(), system(), passthru(), exec(), shell_exec(), o popen() si prestano ad una vulnerabilità che permette di iniettare del codice php e vederlo eseguito dal server.

Nel seguente script cmd.php:

<?php
    echo "<p>exec: ".exec($_GET["cmd"])."</p>";
    echo "<p>passthru: ".passthru($_GET["cmd"])."</p>";
    echo "<p>system: ".system($_GET["cmd"])."</p>";
    echo "<p>shell_exec: ".shell_exec($_GET["cmd"])."</p>";
    echo "<p>popen: ".popen($_GET["cmd"])."</p>";
    echo "<p>eval: ".eval($_GET["cmd"])."</p>";
?>

l’URL:

www.sito.com/cmd.php?cmd=ls

eseguirà il comando passato come argomento usando tutte le funzioni suddette. Il risultato in questo caso sarà l’elenco dei file nella directory corrente in ambiente Linux.

Ecco un esempio di script cancella.php vulnerabile:

<?php
 print("Inserisci il nome del file da rimuovere");
 print("<p>");
 $file=$_GET['nomefile'];
 system("rm $file");
?>

Passando come argomento un file esistente lo script lo rimuove:

www.sito.com/cancella.php?nomefile=testo.txt

Ma cosa succede se invece dell’URL precedente viene richiamato questo:

www.sito.com/cancella.php?nomefile=testo.txt;id

si ottiene la rimozione del file testo.txt ed inoltre l’esecuzione e del comando id che restituisce l’identificativo dell’utente che esegue lo script, tipicamente il web server:

uid=33(www-data) gid=33(www-data) groups=33(www-data)

Per ovviare a questo problema è sufficiente validare l’input con la funzione escapeshellarg() modificando il file cancella.php in questo modo:

<?php
 print("Inserisci il nome del file da rimuovere");
 print("<p>");
 $file = escapeshellarg($_GET['nomefile']);
 system("rm $file");
?>

oppure si possono validare più argomenti concatenati con la funzione escapeshellcmd():

<?php
 print("Inserisci il nome del file da rimuovere");
 print("<p>");
 $file = escapeshellcmd($_GET['nomefile']);
 system("rm $file");
?>

 

Conclusioni

Come si vede esistono numerose pratiche di programmazione scorrette che possono tradursi in pericolose falle nella sicurezza di un sito web. Conoscerle significa evitare o quantomeno ridurre molto la possibilità che malintenzionati possano introdursi nei vostri sistemi.

La lista non pretende di essere esaustiva, per proporre le vostre integrazioni potete utilizzare i commenti.