Prevenire le iniezioni SQL in PHP (e altre vulnerabilità)
Se sei stato in giro per lo sviluppo web per un po’, hai quasi certamente sentito il termine “SQL injection” e alcune storie terrificanti su di esso.
PHP, come molti altri linguaggi, non è immune da questo tipo di minaccia, che può essere davvero molto pericolosa. Ma, per fortuna, proteggere i vostri siti web da SQL injection e altre minacce simili è qualcosa che si può prendere misure tangibili verso.
In questo post, imparerai cos’è SQL injection, quali sono le conseguenze di un attacco riuscito, come un utente malintenzionato può sfruttare il codice PHP vulnerabile, cosa puoi fare per prevenirlo e quali strumenti puoi utilizzare per rilevare le parti del tuo codice che potrebbero essere soggette a questo tipo di minaccia. Inoltre, imparerai a conoscere alcune altre vulnerabilità comuni di cui dovresti essere a conoscenza per creare applicazioni più sicure.
Come funziona un’iniezione SQL?
La prima cosa che devi sapere per proteggere il tuo codice da SQL injection è capire come potrebbe essere sfruttato da un utente malintenzionato.
L’idea alla base dell’exploit è piuttosto semplice: un utente malintenzionato esegue codice SQL dannoso sul database tramite l’app.
Come si potrebbe raggiungere questo obiettivo? Abusando degli input della tua applicazione.
Diamo un’occhiata a un semplice esempio. Supponiamo di avere un’applicazione che mostra un elenco di nomi utente, in cui ogni utente ha un link ai propri dettagli come questo:
E poi, nel file user_details.php
, hai questo:
<?php $sql = "SELECT * FROM user WHERE id = ".$_GET;
Se l’utente agisce come previsto e fa clic sul nome di un utente, l’URL della richiesta sarà simile a questo: http://domain.com/user_details.php?id=8
.
Quindi, cosa c’è di sbagliato?
Il valore di $sql
sarà la stringa "SELECT * FROM user WHERE id = 8"
e la query verrà eseguita correttamente.
Il problema è nella parte in corsivo di questa frase: se l’utente agisce come previsto e fa clic sul nome di un utente, l’URL della richiesta sarà simile a questo:
http://domain.com/user_details.php?id=8
.
Cosa succede se l’utente non agisce come previsto?
Cosa succede se qualcuno dovesse produrre un URL inviando qualcosa di diverso da un numero come ID?
URL fabbricati
Se l’URL fosse qualcosa come http://domain.com/user_details.php?id=Peanuts
, la stringa SQL risultante sarebbe: "SELECT * FROM user WHERE id = Peanuts"
.
In questo caso, il problema sarebbe per l’utente. La query fallirebbe semplicemente e, nel peggiore dei casi, l’utente vedrebbe una sorta di messaggio strano.
Ma cosa succede se l’URL assomigliava più a questo:
http://domain.com/user_details.php?id=1+OR+1%3B
? (Questo è il risultato di urlencode('1 OR 1;')
, a proposito.)
In questo caso, l’SQL risultante sarebbe "SELECT * FROM user WHERE id = 1 OR 1;"
. Ciò significa che il risultato della query sarebbe ogni utente nel database anziché solo l’utente il cui ID è uguale a 1. Ahi.
Ma non è ancora male, giusto?
Ora proviamo un esempio più estremo.
Supponiamo che l’URL fosse qualcosa di simile
http://domain.com/user_details.php?id=1%27%3B+DROP+TABLE+users%3B--+
, che si traduce in id=1; DROP TABLE users;--
, producendo efficacemente questo comando SQL:
"SELECT * FROM users WHERE id = 1; DROP TABLE users;-- "
. E se si esegue questa query big grande ahi.
Nel caso in cui non si abbia familiarità al 100% con la sintassi SQL, DROP TABLE
eliminerebbe completamente la tabella. Sarebbe come se non fosse mai esistito.
Naturalmente, un backup recente aiuterebbe a ridurre i danni, ma ancora. Non va bene.
Se vuoi saperne di più su questo tipo di attacco, c’è un ottimo post qui.
Ma penso che sia abbastanza cattiva notizia. Passiamo a un posto più allegro: come puoi prevenire questi tipi di iniezioni SQL nelle tue app PHP.
Come prevenire le iniezioni SQL in PHP
Come hai visto, il problema è quando un utente malintenzionato ha la capacità di eseguire codice SQL senza che tu ne sia a conoscenza.
Questa situazione si presenta come conseguenza della creazione di query al volo tramite concatenazione di stringhe e inclusione di dati raccolti da input esterni (di solito input dell’utente, ma anche altre fonti esterne come file, risposte alle chiamate API e così via).
Quindi, al fine di prevenire questa vulnerabilità nel codice, è necessario correggere le fonti di potenziali problemi.
Convalida i tuoi input
Un modo semplice per correggere la vulnerabilità nell’esempio precedente sarebbe quello di verificare se il parametro ID è ciò che ci si aspetta effettivamente da esso (cioè, un intero positivo):
Un altro modo per fare questo tipo di convalida è sfruttare i filtri incorporati di PHP:
<?php
$id = filter_input( INPUT_GET, 'id', FILTER_VALIDATE_INT);
Quindi, convalidando i tuoi input, impedisci agli aggressori di eseguire codice dannoso insieme al tuo.
Non c’è un modo semplice per impedire loro di provare, ma finché i loro tentativi non hanno successo, sei a posto.
Utilizzare le istruzioni preparate
Un altro modo per proteggere il codice dalle iniezioni SQL è utilizzare le istruzioni preparate. Le istruzioni preparate sono comandi SQL precompilati.
Possono essere utilizzati con una libreria di accesso al database specifica (come mysqli) o con la libreria più generica DOP.
Diamo un’occhiata a un esempio usando mysqli:
Se provi questo codice (sostituendo le credenziali del database, ovviamente), vedrai che, se accedi tramite un URL legale come http://domain.com/user_details.php?id=1
, otterrai lo stesso risultato di uno qualsiasi degli URL dannosi sopra (che sono le informazioni sull’utente associato a ID 1 e nient’altro).
Se preferisci usare PDO, il codice sarà più simile a questo:
Non c’è molta differenza, giusto?
In sintesi, le dichiarazioni preparate offrono un ottimo modo per prevenire le iniezioni SQL. Inoltre, di solito funzionano meglio di SQL on-the-fly.
Ora che conosci lo strumento, devi solo usarlo.
Come rilevare il codice PHP vulnerabile alle iniezioni SQL
Una volta trovata la vulnerabilità, risolverlo non è davvero complicato. Ma come si fa a trovare questi tipi di vulnerabilità nel codice, in primo luogo?
Bene, se sei fortunato e la tua base di codice è abbastanza piccola, una semplice revisione del codice lo farà.
Ma se la tua applicazione è di medie e grandi dimensioni, avrai bisogno di aiuto extra.
Uno strumento molto popolare (e open-source) è possibile utilizzare per questo scopo è sqlmap, un semplice script Python che cercherà di attaccare qualsiasi URL si alimenta ad esso e riferire sui suoi risultati.
Ecco un output di esempio da una corsa contro un mio sito Web basato su PHP:
Un altro strumento open-source che è possibile utilizzare è Arachni, uno strumento più sofisticato che include una GUI web ed è stato scritto in Ruby.
Ecco un output di esempio dall’esecuzione attraverso la CLI:
Oltre alla revisione e al test del codice, è consigliabile utilizzare una soluzione RASP, come Sqreen, per monitorare gli ambienti di produzione e interrompere l’esecuzione di qualsiasi exploit che superi le fasi di revisione e test.
Quali altre vulnerabilità dovresti essere a conoscenza?
Codice iniezioni
SQL injection è solo un membro di una famiglia più grande di vulnerabilità: codice iniezione.
L’idea alla base di diverse tecniche di iniezione di codice è sempre la stessa. Si tratta di ingannare un’applicazione innocente in esecuzione di codice dannoso.
Questo può essere fatto in diversi modi.
In PHP, ci sono funzioni progettate per emettere semplicemente comandi direttamente nel sistema operativo, come la funzione exec
.
Ecco un semplice esempio di come questo potrebbe essere sfruttato:
<?php
exec( 'cp uploads/'.$_GET.' /secure/');
Se qualcuno fosse a richiesta http://domain.com/exec.php?file=a.txt%3B+rm+-Rf+%2A+%23
, il risultato dovrebbe essere l’esecuzione di questo comando:
cp uploads/a.txt; rm -Rf * # /secure/
E questo potrebbe cancellare l’intero disco sul server—non convalidati ingresso colpisce ancora!
Cross-site scripting
Un’altra vulnerabilità molto comune è cross-site scripting (XSS). In questo caso, la vittima è l’utente che visita il tuo sito.
XSS è quando un utente malintenzionato inietta codice JavaScript dannoso all’interno di un normale modulo HTML, che verrà successivamente reso dal browser di un altro utente.
Guarda questo script PHP:
<?php
echo "<html><p>Hello {$_GET}!</p></html>";
Chiaramente, l’intenzione qui è quella di creare una semplice pagina che saluta l’utente.
Ma quando i cattivi arrivano, cosa impedisce loro di richiedere
http://domain.com/greet.php?name=Mauro%3Cscript+language%3D%22javascript%22%3Ealert%28%22You+are+hacked%21%22%29%3B%3C%2Fscript%3E
? Questa richiesta produrrà il seguente HTML come output:
<html><p>Hello Mauro<script language="javascript">alert("You are hacked!");</script></p></html>
Questo è chiaramente un exploit molto ingenuo-dopo tutto, qual è il danno nel mostrare a qualcuno un semplice ” Sei hackerato!”messaggio? Ma le cose possono diventare piuttosto gravi con un po ‘ di immaginazione. Tieni presente che ogni richiesta fatta a un server include il cookie di sessione, quindi questa vulnerabilità può essere il colpevole dietro le sessioni degli utenti che vengono dirottate.
Poi di nuovo, mentre i problemi sembrano brutti, le correzioni sono davvero semplici: si tratta di convalida e sanificazione.
Nel caso di XSS, una semplice chiamata a htmlentities
prima di produrre l’output effettivo farà.
Fixing vs. detecting security vulnerabilities
Come hai visto in questo post, fixing security vulnerabilities—both SQL injection and other types—is not the main challenge. Rendendosi conto che il codice è vulnerabile, e dove può essere sfruttato, è. Hai un paio di opzioni qui:
- Avere una forte disciplina di codifica
- Testare le applicazioni a fondo per problemi di sicurezza
- Sfruttare strumenti di monitoraggio e protezione per prevenire exploit e ottenere avvisi in modo tempestivo
Quale si sceglie dipende da voi. L’importante è prestare attenzione a questi problemi e agire su quella conoscenza. Stai al sicuro.