Warning: Undefined array key "tbm_guide_level" in /data/websites/htmlit/web/app/themes/htmlit/src/ViewModel/Post/Templates/SingleGuide.php on line 113

Warning: Trying to access array offset on value of type null in /data/websites/htmlit/web/app/themes/htmlit/src/ViewModel/Post/Templates/SingleGuide.php on line 113

Warning: Undefined array key "tbm_guide_level" in /data/websites/htmlit/web/app/themes/htmlit/src/ViewModel/Post/Templates/SingleGuide.php on line 113

Warning: Trying to access array offset on value of type null in /data/websites/htmlit/web/app/themes/htmlit/src/ViewModel/Post/Templates/SingleGuide.php on line 113

Warning: Undefined array key "tbm_guide_level" in /data/websites/htmlit/web/app/themes/htmlit/src/ViewModel/Post/Templates/SingleGuide.php on line 113

Warning: Trying to access array offset on value of type null in /data/websites/htmlit/web/app/themes/htmlit/src/ViewModel/Post/Templates/SingleGuide.php on line 113
Proteggersi dalla SQL Injection | HTML.it
Nessun risultato. Prova con un altro termine.
Guide
Notizie
Software
Tutorial

Proteggersi dalla SQL Injection

Conoscere ed evitare un classico errore di programmazione che incide sulla sicurezza del proprio sito Web
Conoscere ed evitare un classico errore di programmazione che incide sulla sicurezza del proprio sito Web
Link copiato negli appunti

Introduzione

La creazione di contenuti dinamici che consentano all'utente di interagire con il nostro sito sono spesso alla base del successo o dell'insuccesso dei servizi che offriamo tramite il web. Maggiore è l'importanza dei dati che gestiamo, maggiore sarà anche il bisogno di sicurezza che ruota attorno ai dati stessi. La sicurezza di un sito web non viene garantita soltanto da un web server ben configurato, o da un tunnel SSL, ma deve essere implementata in maniera coscienziosa anche da chi sviluppa l'applicazione web, nel nostro caso il programmatore PHP. In questo articolo andremo a conoscere una delle più classiche tipologie di attacco legate al web, molto diffusa ma spessa sottovalutata, che va a colpire il cuore dell'applicazione web, ossia il database: si tratta dell'attacco di tipo SQL Injection. È importante tenere presente che questo fenomeno può interessare qualsiasi linguaggio di programmazione e qualsiasi DBMS, anche se gli esempi proposti faranno riferimento a PHP, con accenni a MySQL e PostgreSQL.

SQL Injection in teoria

Il problema è relativamente semplice da capire, ma è anche molto pericoloso: effettuando una query SQL costruita sulla base di input passati
da un utente, senza eseguire un controllo preventivo sullo stesso input, tale query può essere manipolata a piacimento.
L'input dell'utente
nel nostro caso può esserci trasmesso in vari modi: tramite URL (query string), tramite un form HTML oppure anche tramite un cookie costruito
su misura. Non tutti gli utenti utilizzano il nostro sito in modo "ortodosso", ad esempio cliccando su un link o compilando correttamente un
modulo, per cui i dati che ci arrivano potrebbero non rispettare le nostre aspettative.

Che tipo di problemi può portare una query manipolata arbitrariamente da un utente?

  • manipolazione indesiderata dei nostri dati
  • accesso indesiderato ad aree riservate
  • visualizzazione di dati privati
  • SQL Injection in pratica

    Supponiamo ad esempio di avere una variabile $id presa in input dalla query string, teoricamente di tipo intero ma non correttamente
    validata
    . Una query del tipo $sql = "SELECT * from articoli WHERE id=$id"; potrebbe essere manipolata a piacere da un utente smaliziato, causando problemi di varia natura. Che cosa succede se $id anzichè essere un numero intero viene passata come stringa, ad esempio "1; DROP table articoli"? La query verrebbe trasformata in questo modo:

    Un altro esempio per renderci conto della gravità del problema:

    $sql = "SELECT * from utenti WHERE login='$login' AND password='$password'";

    Nel caso in cui non vengano effettuati controlli, una query del genere può essere comodamente modificata manipolando $login, che potrebbe diventare ad esempio "pippo' OR 1=1 --". Da notare l'apice all'interno di $login, che assume un valore particolare nella query SQL, l'operatore OR che aggiunge una clausula fittizia (1=1 è sempre vero) ed i caratteri -- che in SQL corrispondono ad un commento. La query è quindi
    diventata:

    SELECT * from utenti WHERE login='pippo' OR 1=1 --' AND password=''

    in sostanza una query del genere restituisce il record corrispondente al login pippo, senza bisogno di inserire anche una password. Ecco quindi il bisogno di implementare una serie di controlli preventivi con lo scopo di arginare ogni possibile falla

    Le vulnerabilità possono essere numerose (dipende dalla fantasia dell'attaccante) e derivano solitamente da una distrazione del programmatore errata implementazione

    Al lato pratico, la situazione può sembrare forse meno pericolosa di quanto abbiamo visto fino a questo momento. Ad esempio MySQL non supporta gli statement multipli, per cui una query come quella del primo esempio produrrebbe soltanto un messaggio d'errore. Nessun danno quindi, ma l'errore deriva comunque da un'errata implementazione, per cui è importante correggere in qualche modo il nostro codice. Inoltre una direttiva del file di configurazione php.ini, magic_quotes_gpc
    inserisce un carattere backspace, ) di alcuni caratteri potenzialmente pericolosi, come ad esempio gli apici singoli e doppi, all'interno delle variabili $_GET, $_POST e $_COOKIE. Il secondo esempio diventerebbe quindi innocuo, in quanto l'escape dell'apice lo forzerebbe ad essere interpretato non come codice SQL, ma come un normale carattere:

    SELECT * from utenti WHERE login='pippo' OR 1=1 --' AND password=''

    Una query del genere probabilmente non restituirà alcun risultato. Naturalmente non possiamo fare affidamento al caso, sperando che un determinato server sia configurato in un certo modo, ma dobbiamo
    cercare di trovare una soluzione che ci consenta di ottenere un livello di sicurezza ragionevole

    Affrontiamo il problema

    Non esiste una soluzione assoluta, che sia sicura e portabile allo stesso tempo. Lo stesso concetto di sicurezza è relativo e viene influenzato da numerosi parametri. Il nostro approccio al problema dovrà essere abbastanza elastico e dovremo adattarci alle situazioni, scegliendo di volta in volta la soluzione più opportuna.

    È stato detto che il punto fondamentale è saper gestire l'input dell'utente, e sarà qui che andremo a focalizzare la nostra attenzione. A seconda dei dati che andremo a trattare, possiamo adottare più strategie per elevare il livello di sicurezza:

    • Controlli sul tipo di dato
    • Creazione di filtri tramite espressioni regolari
    • Eliminazione di caratteri potenzialmente dannosi
    • Escape di caratteri potenzialmente dannosi
    • Tipi di dato e type casting

      Il type casting è un'operazione che forza una variabile ad essere valutata come appartenente ad un certo tipo. Nel primo esempio di query manipolata, ci aspettiamo che la variabile $id sia di tipo intero, ma per evitare sorprese, è opportuno essere sicuri di ciò:


      $sql = "SELECT * from articoli WHERE id=$id";

      In questo modo $id avrà sicuramente un valore intero. Tendando di modificare in qualche modo la query string, un attaccante non sarà comunque in grado di eseguire istruzioni SQL arbitrarie. Il type casting può essere eseguito anche mediante la funzione settype()

      settype($_GET['id'], 'int'); // forzo la variabile ad essere di tipo intero
      $id = $_GET['id'];

      o ancora, nel caso degli interi, con la funzione intval()

      $id = intval($_GET['id']);

      Un approccio leggermente diverso ma sempre legato al tipo di variabili in gioco consiste nel verificare, mediante opportune funzioni come is_int()
      is_numeric()
      gettype()

      if (is_numeric($_GET['id'])) {
      // ho un valore numerico, posso procedere
      }

      Nota stringhe numeriche

      Espressioni regolari

      Spesso i dati che aspettiamo in input possono essere descritti da una espressione regolare. Ad esempio supponiamo che lo username di un utente registrato possa essere una stringa alfanumerica composta da un minimo di 4 ed un massimo di 12 caratteri. Per filtrare dati che non rispettano questi vincoli possiamo utilizzare la funzione preg_match() oppure la funzione ereg(), ad esempio:


      // $login rispetta il parametro, per cui posso effettuare la query
      }
      else {
      // errore
      }

      Per una trattazione più approfondita sulle espressioni regolari vi rimando ad un
      Espressioni regolari

      Eliminazione di caratteri pericolosi

      Quando il type casting non ci può servire e non troviamo un'espressione regolare che possa filtrare adeguatamente un input, possiamo decidere di eliminare eventuali caratteri pericolosi o sostituirli con codici innocui. In sostanza i caratteri potenzialmente dannosi sono quelli che hanno un significato all'interno di una query SQL, come ad esempio gli apici singoli e doppi, la virgola, il punto e virgola, e così via. In alcune situazioni tali caratteri devono essere ammessi: se un nostro utente si chiama ad esempio "Paperon de' Paperoni", l'apice fa parte del suo cognome, e sarà opportuno concederne l'inserimento in un ipotetico form di registrazione. In altri casi invece questi caratteri possono essere eliminati o sostituiti senza problemi, sfruttando una tra le funzioni
      str_replace(),
      preg_replace(),
      ereg_replace() o
      strtr(), ad esempio:
      $input = str_replace("'", "", $input); // attenzione ai caratteri inseriti.

      Il primo parametro di str_replace() è la stringa da cercare (nell'esempio, un apice singolo), il secondo è la stringa di sostituzione (una stringa vuota), mentre il terzo è la stringa di partenza (una ipotetica variabile $input). Un approccio del genere può comunque risultare difficile da applicare, in quanto i caratteri da controllare possono essere svariati ed i DBMS si comportano in maniera differente, per cui
      sarà facile dimenticare qualche particolare.

      Escape delle stringhe

      Come accennato nei primi paragrafi, PHP mette a disposizione la possibilità di effettuare automaticamente l'escape di alcuni caratteri tramite
      la direttiva magic_quotes_gpc
      che per default è attiva e che opera sulle variabili $_GET, $_POST e $_COOKIE. Questa può essere una prima
      forma di sicurezza, ma non possiamo dare per scontato che qualsiasi server su cui potrà girare la nostra applicazione abbia
      questa impostazione. Per verificare lo stato di magic_quotes_gpc, possiamo usare la funzione
      get_magic_quotes_gpc()
      che ci restituisce 1 se l'opzione è attiva, oppure 0 se l'opzione è stata disattivata. Il risultato ottenuto con magic_quotes_gpc attivo
      può essere generato manualmente anche con la funzione
      addslashes()
      che va ad aggiungere un carattere backspace prima di apici singoli, apici doppi, altri backspace o caratteri NUL.
      Se andiamo ad effettuare un addslashes() quando magic_quotes_gpc è attivo, il risultato sarà probabilmente diverso da quello che
      ci aspettiamo:


      viene trasformata da magic_quotes_gpc: "stringa"
      aggiungendo anche addslashes() il risultato è: "stringa"

      e la situazione risulta scomoda da gestire. Controllando lo stato di magic_quotes_gpc possiamo fare qualcosa di meglio:

      // con magic_quotes_gpc disattivo, mi appoggio ad addslashes()
      if ( ! get_magic_quotes_gpc() ) {
        $_GET['variabile'] = addslashes($_GET['variabile']);
      }

      Un approccio più consigliato è però l'utilizzo delle funzioni specifiche relative al DBMS che stiamo utilizzando. In questo caso è opportuno fare riferimento alla pagina di manuale relativa al DBMS che ci interessa. Nel caso dei due più importanti database opensource abbiamo a disposizione le funzioni
      mysql_escape_string()
      mysql_real_escape_string()
      oppure pg_escape_string()


      Abbiamo visto come sia possibile trasformare una innocente query SQL in una istruzione potenzialmente distruttiva per il nostro database, ed
      abbiamo visto quanto sia relativamente semplice effettuare una serie di controlli che hanno lo scopo di limitare i problemi. Le soluzioni che
      proposte, type casting, controllo del tipo di dato, espressioni regolari, eliminazione o escape delle stringhe, possono comunque essere
      integrate tra di loro.
      Quando ci si trova davanti ad input diversi da quelli attesi ci sono più vie da scegliere, a seconda delle situazioni e della propria opinione
      personale: è possibile utilizzare dei valori predefiniti per sostituire gli input non regolari, oppure si può scegliere di predisporre un
      messaggio d'errore, o anche di costruire un sistema di logging che registri tutti i tentativi di exploit.

      Indipendentemente dalla via che si decide di intraprendere, l'unico punto che dobbiamo avere ben chiaro è: controllare ogni input.

      Oltre ai link alle pagina del manuale presenti nell'articolo, ecco alcuni riferimenti per approfondire l'argomento:

Ti consigliamo anche