Loading AI tools
tecnica di code injection Da Wikipedia, l'enciclopedia libera
Nella sicurezza informatica SQL injection è una tecnica di command injection, usata per attaccare applicazioni che gestiscono dati attraverso database relazionali sfruttando il linguaggio SQL. Il mancato controllo dell'input dell'utente permette di inserire artificiosamente delle stringhe di codice SQL che saranno eseguite dall'applicazione server: grazie a questo meccanismo è possibile far eseguire comandi SQL, anche molto complessi, dall'alterazione dei dati (es. creazione di nuovi utenti) al download completo dei contenuti nel database.[1]
Le prime discussioni pubbliche relative all'SQL injection sono apparse attorno al 1998.[2] Per esempio, un articolo del 1998 su Phrack Magazine.[3]
Sfrutta le vulnerabilità di sicurezza del codice applicativo che si collega alla fonte dati SQL, ad esempio, sfruttando il mancato filtraggio dell'input dell'utente (es. 'caratteri di escape' nelle stringhe SQL) oppure la mancata tipizzazione forte delle variabili impiegate. È più conosciuto come attacco destinato ad applicazioni web, ma è anche usato per attaccare qualsiasi altro tipo di applicazione che impieghi in modo non sicuro database SQL.
Permette agli attaccanti di effettuare attività di spoof identify, modificare dati esistenti, causare repudiation issues come l'annullamento di transazioni o la modifica dei bilanci, permette di ottenere tutti i dati sul sistema, eliminare o rendere inaccessibile dei dati, e creare o compromettere gli utenti (e.g. amministratore) del database server.
In uno studio del 2012, è stato osservato che in media le applicazioni web ricevono 4 attacchi al mese, ed i rivenditori ricevono il doppio degli attacchi rispetto alle industrie.[4]
L'SQL injection (SQLI) è considerata da Open Web Application Security Project una delle 10 maggiori vulnerabilità delle applicazioni web nel 2007 e nel 2010.[5] Nel 2013 e nel 2017 SQLI è stato considerato il numero uno degli attacchi sulla OWASP top 10.[6] Ci sono quattro principali sotto classi di SQL injection:
Lo Storm Worm è un esempio di utilizzo delle Compounded SQLI.[11]
Questa classificazione dello stato dell'SQLI rappresenta la sua evoluzione fino al 2010.[12]
Questo tipo di SQL injection si verifica quando non viene filtrato l'input dell'utente dai caratteri di escape e quindi vengono passati all'interno di uno statement. Questo può provocare la manipolazione degli statements eseguiti sul database dagli utenti finali dell'applicazione.
La seguente linea di codice illustra questo tipo di vulnerabilità:
statement = "
SELECT * FROM users WHERE name = '
"
+ userName + "
';
"
Questo codice SQL recupera tutti i record che hanno un certo username dalla tabella users. Tuttavia, se un utente malintenzionato scrive la variabile “userName” in un certo modo, lo statement SQL può fare più di quello che era inteso dall'autore del codice. Per esempio, impostando la variabile “userName” come:
' OR '1'='1
Oppure usando dei commenti per non fare eseguire il resto della query (ci sono tre tipi di commenti SQL[13]). Tutte e tre le linee hanno uno spazio alla fine:
' OR '1'='1' -- ' OR '1'='1' ({ ' OR '1'='1' /*
Ecco come appare il rendering dello statement SQL dopo l'inserimento di una delle linee di codice con commento e senza:
SELECT * FROM users WHERE name = '' OR '1'='1';
SELECT * FROM users WHERE name = '' OR '1'='1' -- ';
Se questo codice fosse utilizzato in una procedura di autenticazione, allora questo esempio potrebbe essere usato per forzare la selezione di tutti i campi dati (*) di "tutti" gli utenti piuttosto che di un singolo username come era inteso dal codice, ciò accade perché la valutazione di ‘1'='1' è sempre vera (short-circuit evaluation).
Nell'esempio di sotto il valore di “userName” causerebbe l'eliminazione della tabella “user” e la selezione di tutti i dati nella tabella “userinfo” (in pratica rivelando le informazioni di tutti gli utenti), usando un API che permette statement multipli:
a'; DROP TABLE users; SELECT * FROM userinfo WHERE 't' = 't
L'inserimento dell'input specificato sopra fa diventare lo statement SQL in questo modo:
SELECT * FROM users WHERE name = 'a';DROP TABLE users; SELECT * FROM userinfo WHERE 't' = 't';
Mentre molte delle implementazioni dei server SQL permettono di eseguire statement multipli con un'unica chiamata, alcune di queste API come nella funzione mysql_query()
di PHP non lo permettono per motivi di sicurezza. Questo evita che gli attaccanti iniettino delle query completamente separate all'interno dello statement, ma ciò non li ferma dal modificarle.
Questo tipo di SQL injection si verifica quando un campo fornito dall'utente non è fortemente tipizzato o non vengono controllati i vincoli sul tipo. Questo può accadere, ad esempio, quando viene utilizzato un campo numerico in uno statement SQL, ma il programmatore non fa controlli per verificare che l'input immesso dall'utente sia effettivamente numerico. Per esempio:
statement:= "SELECT * FROM userinfo WHERE id =
" + a_variable + ";"
Da questo statement è evidente che l'autore voleva che la variabile fosse un numero riferito al campo "id". Tuttavia se essa è di fatto una stringa, allora l'utente finale potrebbe manipolare lo statement a suo piacimento, e quindi aggirare la necessità di caratteri di escape. Per esempio, impostando una variabile a
1;DROP TABLE users
Viene fatto il drop (eliminazione) della tabella "users" dal database, visto che lo statement SQL diventa:
SELECT * FROM userinfo WHERE id=1; DROP TABLE users;
Il Blind SQL Injection è usato quando un'applicazione web è vulnerabile ad SQLI ma i risultati dell'operazione non sono visibili all'attaccante. La pagina con la vulnerabilità potrebbe non essere una che mostra dei dati, ma può essere visualizzata differentemente a seconda del risultato dello statement di tipo logico iniettato dentro lo statement SQL originale, chiamato per quella pagina. Questo tipo di attacco può impiegare un notevole dispendio di tempo perché bisogna creare un nuovo statement per ogni bit recuperato. Ci sono vari strumenti che permettono di automatizzare questi attacchi una volta che sono state individuate le vulnerabilità e qual è l'informazione obiettivo.[14]
Uno dei tipi di blind SQL injection forza il database a valutare uno statement logico su un'ordinaria schermata dell'applicazione. Ad esempio, un sito di recensioni sui libri usa una query string per decidere che recensione su quale libro mostrare. Quindi l'URL http://books.example.com/showReview.php?ID=5
farà eseguire al server la query
SELECT * FROM bookreviews WHERE ID = 'Value(ID)';
Con la quale popolerà la pagina con i dati della recensione con ID 5, memorizzati nella tabella bookreviews. La query viene eseguita completamente sul server; quindi l'utente non saprà i nomi del database, della tabella, o dei campi, e nemmeno conoscerà la query string. L'utente vedrà soltanto che l'URL di sopra ritorna la recensione di un libro. Un Hacker può caricare i seguenti URL
e http://books.example.com/showReview.php?ID=5 OR 1=1
, che potrebbero generare rispettivamente l'esecuzione di queste query:http://books.example.com/showReview.php?ID=5 AND 1=2
SELECT * FROM bookreviews WHERE ID = '5' OR '1'='1';
SELECT * FROM bookreviews WHERE ID = '5' AND '1'='2';
Se utilizzando l'URL con "1=1" viene caricata la recensione originale e con l'URL che ha "1=2" viene caricata una pagina bianca o d'errore, e la pagina ritornata non mostra all'utente che è appena stato inserito un URL non valido, è molto probabile che il sito sia vulnerabile ad attacchi di SQL injection perché vuol dire che entrambe le query potrebbero essere state eseguite correttamente dal database server. L'hacker potrebbe eseguire la seguente query string per venire a conoscenza del numero di versione di MySQL presente sul server:
, una volta eseguita mostrerebbe la recensione del libro se il server utilizza MySQL 4 ed altrimenti una pagina bianca o d'errore. L'hacker può continuare ad usare del codice all'interno delle query string per ottenere sempre più informazioni sul server fino a quando non viene scoperta una nuova via di attacco oppure fino a quando i suoi obiettivi non siano raggiunti.[15][16]http://books.example.com/showReview.php?ID=5 AND substring(@@version, 1, INSTR(@@version, '.') - 1)=4
Il Second order SQL injection si verifica quando i valori inviati dall'utente contengono comandi maligni che vengono salvati sul server piuttosto che venire eseguiti immediatamente. In alcuni casi, l'applicazione può memorizzare l'input maligno come se fosse uno statement SQL valido, ed un'altra parte dell'applicazione che non effettua controlli per proteggersi da SQL injections potrebbe eseguire lo statement memorizzato. Questo tipo di attacco richiede la conoscenza che i valori inviati vengano utilizzati successivamente. Gli scanner di sicurezza per applicazioni web potrebbero non accorgersi facilmente di questo tipo di SQL injection e potrebbero aver bisogno di essere istruite manualmente su dove cercare indizi sull'attacco.
L'SQL injection è un attacco molto conosciuto e facilmente evitabile con semplici misure. Dopo quello che si pensa fosse stato un attacco di SQL injection sull'azienda britannica TalkTalk Group, la BBC ha riportato come gli esperti di sicurezza fossero stupiti che una così grande compagnia fosse vulnerabile a questo tipo di attacco.[17]
Con molte delle piattaforme di sviluppo, è possibile usare statement parametrizzati che lavorano con dei parametri (chiamati placeholder o bind variable) al posto di inserire direttamente l'input dell'utente direttamente nello statement. Un placeholder può memorizzare soltanto un valore del tipo specificato e non un qualsiasi statement SQL. In questo modo l'SQL injection viene trattata semplicemente come un valore non valido per quel parametro.
In molti casi, lo statement SQL viene invece fissato, ed ogni parametro è quindi uno scalare, e non una tabella. L'input dell'utente è quindi assegnato (legato) ad un parametro.[18]
Usare delle librerie di object-relational mapping evita di scrivere codice SQL. Le librerie ORM generano automaticamente degli statement SQL parametrizzati, partendo da del codice orientato agli oggetti.
Un modo diretto, anche se soggetto ad errori per prevenire attacchi di SQLI è quello di evitare caratteri che hanno un significato speciale in SQL. I manuali dei DBMS SQL spiegano quali caratteri hanno significati speciali, ciò permette di creare una lista di caratteri che devono essere sostituiti perché potenzialmente dannosi. Ad esempio, ogni apostrofo ('
) in un parametro deve essere sostituito con due apostrofi (' ') per ottenere una stringa SQL di letterali valida. Per esempio, in PHP di solito si evitano i caratteri speciali nei parametri utilizzando la funzione mysqli_real_escape_string();
prima di inviare la query SQL:
$mysqli = new mysqli('hostname', 'db_username', 'db_password', 'db_name');
$query = sprintf("SELECT * FROM `Users` WHERE UserName='%s' AND Password='%s'",
$mysqli->real_escape_string($username),
$mysqli->real_escape_string($password));
$mysqli->query($query);
Questa funzione antepone dei backslash ai seguenti caratteri: \x00
, \n
, \r
, \
, '
, "
e \x1a
.
Essa è di solito usata per rendere sicure le query prima di inviare ad un database MySQL.[19]
In PHP vi sono tante altre funzioni per vari tipi di database come pg_escape_string()
per PostgreSQL. La funzione addslashes(string $str)
serve ad evitare i caratteri speciali, ed è usata in particolare per fare query a database che non hanno funzioni di escape in PHP, essa ritorna una stringa con dei backslash prima dei caratteri che hanno bisogno di essere tra apici. Questi caratteri sono l'apostrofo ('), doppi apici ("), il backslash ed il carattere NULL.[20]
Fare sempre l'escape delle stringhe SQL è una pratica soggetta ad errori perché è facile dimenticare di fare l'escape di una determinata stringa. Creare un livello trasparente per rendere sicuro l'input può ridurre gli errori o eliminarli totalmente.[21]
Si possono controllare parametri con stringhe di interi, float o booleani, e verificare se il loro valore ha una valida struttura per il tipo di dato controllato. Le stringhe che hanno una struttura abbastanza rigida (data, UUID, solo alfanumerici, ecc.) possono essere controllati per vedere se rispecchiano la struttura del tipo di dato.
Limitare i permessi nel login usato dell'applicazione web per accedere al database, mettendo solo i permessi necessari, può servire a ridurre l'efficacia di qualsiasi attacco di SQL injection, mirato a dei bug dell'applicazione.
Per esempio, su Microsoft SQL Server, si può proibire al logon del database di fare una select ad alcune delle tabelle di sistema, in questa maniera viene evitato che venga inserito del JavaScript all'interno di tutte le colonne del database
deny select on sys.sysobjects to webdatabaselogon;
deny select on sys.objects to webdatabaselogon;
deny select on sys.tables to webdatabaselogon;
deny select on sys.views to webdatabaselogon;
deny select on sys.packages to webdatabaselogon;
La conversione esadecimale viene fatta convertendo del semplice testo nella sua rappresentazione esadecimale prima di usarlo in un comando SQL. In PHP, le funzioni usate sono la funzione bin2hex()[22] o dechex[23]. La funzione bin2hex() è da preferire visto che converte qualsiasi carattere e non solo numeri. In questa sezione verrà usata solo la funzione bin2hex().
Esempio di utilizzo della funzione di PHP bin2hex():
echo bin2hex("test");
L'output della funzione sopra è:
74657374
A scopo d'esempio parliamo soltanto del database MySQL[24]. In MySQL, la funzione unhex()[25] è usata per riconvertire una stringa esadecimale in semplice testo.
Esempio della funzione unhex() in MySQL:
SELECT * FROM myTable WHERE id=unhex('32');
Se convertissimo la stringa con unhex() in semplice testo diventerebbe:
SELECT * FROM myTable WHERE id=2;
La conversione esadecimale elimina gli attacchi di SQL injection perché la stringa esadecimale inviata alla funzione unhex() viene ritornata come stringa già utilizzata e non viene interpretata.
Nel seguente breve programma viene presentato un programma in PHP ed una sua funzione. Il programma mostra un attacco di SQL injection su un semplice comando SQL. Successivamente, mostra come convertendo tutti i dati in entrata in esadecimale, riesce a fermare l'attacco. La funzione PHP è un semplice insieme di comandi per il database e per il recupero dell'output dal database SQL. Come detto sopra il database usato è MySQL.
File: test.php
<?php
include_once "dosql.php";
#
# Metti le informazioni sul tuo database qui. Sto usando i dati del mio file di log.
#
$hostname = "myhost";
$username = "myUser";
$password = "myPassword";
$database = "myDatabase";
$mysqli = new mysqli($hostname, $username, $password, $database);
if ($mysqli->connect_errno) {
echo "Failed to connect to MySQL: (", $mysqli->connect_errno, ") ", $mysqli->connect_error;
exit;
}
echo "SQL INJECTION - Plain\n";
$sql = "SELECT * FROM log WHERE log_id='2' OR 1=1; #'";
$res = dosql($sql);
foreach ($res[0] as $k => $v) {
echo "RES[$k] = $v\n";
}
echo "\n\nSQL INJECTION = Hexadecimal\n";
$sql = "SELECT * FROM log WHERE log_id=unhex('" . bin2hex("2' or 1=1; #'") . "')";
$res = dosql($sql);
foreach ($res[0] as $k => $v) {
echo "RES[$k] = $v\n";
}
exit;
?>
File: dosql.php
<?php
################################################################################
# dosql(). Do the SQL command.
################################################################################
function dosql($sql)
{
global $mysqli;
$cmd = "INSERT INTO log (date,entry) VALUES (NOW(), unhex('" . bin2hex($sql) . "'))";
$res = $mysqli->query($cmd);
$res = $mysqli->query($sql);
if (!$res) {
$array = debug_backtrace();
if (isset($array[1])) { $a = $array[1]['line']; }
else if (isset($array[0])) { $a = $array[0]['line']; }
else { $a = "???"; }
echo "ERROR @ ", $a, " : (", $mysqli->errno, ")\n", $mysqli->error, "\n\n";
echo "SQL = $sql\n";
exit;
}
if (preg_match("/INSERT/i", $sql)) { return $mysqli->insert_id; }
if (preg_match("/DELETE/i", $sql)) { return null; }
if (!is_object($res)) { return null; }
$count = -1;
$array = array();
$res->data_seek(0);
while ($row = $res->fetch_assoc()) {
$count++;
foreach ($row as $k => $v) { $array[$count][$k] = $v; }
}
return $array;
}
SQL INJECTION - Plain
RES[log_id] = 1
RES[date] = 2015-03-25 10:40:18
RES[entry] = SHOW full columns FROM log
SQL INJECTION = Hexadecimal
RES[log_id] = 2
RES[date] = 2015-03-25 10:40:18
RES[entry] = SELECT * FROM log ORDER BY title ASC
La prima parte dell'output del programma mostra l'invio del comando SQL senza fare controllo o modifiche. La richiesta originale dovrebbe far ritornare il secondo record, ma grazie all'SQL injection viene ritornato il primo record. La seconda parte dell'output mostra invece cosa succede se vengono convertiti tutti i dati in ingresso in esadecimale. Usando la conversione, tutti i caratteri nel comando, inclusi quelli inseriti dall'SQL injection, vengono convertiti in esadecimale e non sono più un pericolo perché non vengono interpretati come comandi ma come stringhe. Visto che tutti i caratteri vengono trattati come parte dell'intera stringa, se MySQL capisce che ha bisogno di un numero, converte la stringa in numero. Le regole per la conversione da stringa a numero, sono di convertire[26] la stringa fino a quando non raggiunge un carattere non numerico o la fine della stringa. Quando si verifica uno di questi due eventi, la conversione si arresta in quel punto. Quindi, viene visto il “2” e poi l'apostrofo (‘), il che dice a My SQL di terminare la conversione della stringa. Infine il valore numerico due(2) viene usato per determinare quale record ritornare. Questo processo o metodo di valutazione dei dati in ingresso è il motivo per cui non possono avvenire attacchi di SQL injection se si usa una conversione esadecimale.
In aggiunta, l'utilizzo di BIN2HEX and UNHEX richiede meno tempo d'esecuzione rispetto agli altri metodi presentati. Questo accade principalmente per la natura semplicistica di entrambe le funzioni. Come mostrato nello snippet JavaScript, convertire in esadecimale è piuttosto semplice e diretto:
File: toHex.js
////////////////////////////////////////////////////////////////////////////////
// toHex(). Converte una stringa in esadecimale.
////////////////////////////////////////////////////////////////////////////////
function toHex(s)
{
var l = "0123456789ABCDEF";
var o = "";
if (typeof s != "string") { s = s.toString(); }
for (var i = 0; i < s.length; i++) {
var c = s.charCodeAt(i);
o = o + l[(c >> 4)] + l[(c & 0xf)];
}
return o;
}
Come mostrato, a differenza di mysqli_real_escape_string()[27] con il quale deve essere fatto il test di escape per ogni differente, bin2hex() converte semplicemente tutti i caratteri nel loro corrispettivo esadecimale. E la funzione unhex()fa l'operazione opposta. Visto che non devono essere fatti test per un particolare carattere o caratteri, il ciclo di conversione è piccolo ed efficiente. Inoltre, a differenza di mysqli_real_escape_string(), se in futuro dovesse essere scoperto un nuovo metodo, la funzione bin2hex() disabiliterà automaticamente la combinazione di caratteri, perché ritorna una stringa esadecimale. Un esempio di questo è il carattere Control-D ASCII.[28] Control-D può provocare una condizione "Fine della trasmissione"[29] in alcuni linguaggi. Se viene usata la funzione bin2hex() il Control-D diventa semplicemente il semplice testo ASCII “04” che non può causare problemi.