SQL Injection: alcune buone pratiche per evitarlo

SQL-INJECTION

In questo articolo vedremo alcune buone pratiche di programmazione SQL per attenuare il rischio di SQL Injection. Faremo in particolare riferimento al database SQL Server e quindi al linguaggio T-SQL, studiando:

  • come eseguire codice dinamico tramite la stored procedure sp_executesql invece di exec;
  • l’utilizzo di QUOTENAME;
  • come gestire elenchi di valori da passare dinamicamente ad una IN.

Questo articolo non è certamente esaustivo, per quanto molto dettagliato. Inoltre è importante utilizzare buone pratiche di sviluppo anche sull’eventuale linguaggio che usiamo per interfacciarci con il database (PHP, Java, Python, eccetera).

 

Usiamo i parametri nelle stringhe dinamiche e usiamo sp_executesql

Partiamo guardando un modo di programmare con l’SQL dinamico che non tiene in considerazione i problemi dell’SQL Injection. Per semplicità valorizziamo direttamente la variabile @Nome con un valore d’esempio, che nella realtà potrebbe essere calcolato con una query o passato in input ad una stored procedure

DECLARE @Nome VARCHAR(50) = 'Nicola'; 
DECLARE @SqlString NVARCHAR(4000);
SELECT @SqlString = N'SELECT * 
                      FROM Clienti 
                      WHERE Nome = '''
                      + @Nome 
                      + ''''
SELECT @SqlString;
EXEC (@SqlString)

Ribadiamo che questo non è il modo corretto di eseguire questo tipo di task. In questo caso stiamo inserendo direttamente all’interno della query dinamica il valore della variabile @Nome. Se ad esempio @Nome fosse valorizzato con

'Nicola''; DROP TABLE Clienti; SELECT '''

allora andremo ad eseguire la query

SELECT * FROM Clienti WHERE Nome = 'Nicola'; 
DROP TABLE Clienti; 
SELECT ''

con esiti poco piacevoli. Possiamo fare un importante passo in avanti riscrivendo il codice in questo modo

SELECT * FROM Clienti WHERE Nome = 'Nicola'; 
DROP TABLE Clienti; 
SELECT ''

con esiti poco piacevoli. Possiamo fare un importante passo in avanti riscrivendo il codice in questo modo

DECLARE @Nome VARCHAR(50) = 'Nicola'
DECLARE @SqlString NVARCHAR(4000);
SELECT @SqlString = N'SELECT * 
                      FROM Clienti 
                      WHERE Nome = @par_nome'
EXECUTE sp_executesql @SqlString ,
                      N'@par_nome VARCHAR(50)',
                      @par_nome= @Nome 

In questo modo la stringa che ci viene passata in input sarà utilizzata solamente per eseguire il filtro sulla colonna Nome, ma non entrerà all’interno del codice della query.

 

Utilizzo di QUOTENAME

Quotename è una funzione T-SQL che permette di racchiudere il contenuto di una stringa all’interno di parentesi quadre. Tuttavia è diversa dalla semplice concatenazione con i caratteri “[” e “]” in quanto gestisce anche l’inserimento di caratteri di escape nel caso le parentesi quadre fossero già presenti nella stringa di input. Tuttavia possiamo darle in input una stringa lunga al più 128 caratteri, altrimenti ci restituirà un NULL.

Dopo questa premessa, vediamo anche qui un modo potenzialmente pericoloso di costruire una query dinamica:

DECLARE @Table VARCHAR(50) = 'Clienti'
DECLARE @SqlString NVARCHAR(4000);
SELECT @SqlString = N'SELECT * FROM ' 
                     + @Table 
                     + ';'
SELECT @SqlString
EXECUTE sp_executesql @SqlString

Anche in questo caso se Table fosse valorizzata con

Clienti; DROP TABLE Clienti; SELECT ''''

saremo nei guai. Possiamo risolvere il problema aggiungendo all’inizio la riga

SELECT @Table = QUOTENAME(@Table)

In questo caso in presenza di una stringa malevola andremo semplicemente ad interrogare una tabella che non esiste.

 

Sostituiamo l’IN con una JOIN

Supponiamo di avere una variabile che contenga una lista di numeri interi separati da una virgola. Vogliamo utilizzarla per costruire la clausola IN di una Query che effettua un filtro su una colonna di interi. Anche in questo caso il modo forse “più naturale” di programmare non è quello più corretto.

DECLARE @lista_in VARCHAR(50) = '1,2,3'
DECLARE @SqlString NVARCHAR(4000)
SELECT @SqlString = N'SELECT *
                      FROM Clienti
                      WHERE IdCliente IN ('
                      + @lista_in 
                      + ');'
SELECT @SqlString; 
EXEC sp_executesql @SqlString;

Potremmo infatti costruire abbastanza facilmente una stringa malevola. L’idea per risolvere il problema è quella di creare una subquery con l’elenco distinto di numeri presenti nella lista, per poi riscrivere la query utilizzando una INNER JOIN invece dell’IN. Dalla versione 2016 di SQL Server, possiamo raggiungere questo scopo abbastanza facilmente tramite la funzione STRING_SPLIT. Scriveremo:

DECLARE @lista_in VARCHAR(50) = '1,2,3'
DECLARE @SqlString NVARCHAR(4000)
SELECT @SqlString = N'SELECT *
    FROM Clienti AS C
     INNER JOIN (SELECT DISTINCT 
                  CONVERT(INT,value) AS Id
	          FROM STRING_SPLIT(@par_lista_in,'','')
		 ) AS S
  ON C.IdCliente = S.Id'
EXEC sp_executesql @SqlString,
		   N'@par_lista_in VARCHAR(50)', 
		   @par_lista_in= @lista_in

Alcune osservazioni sulla query;

  • la funzione STRING_SPLIT non garantisce l’ordinamento dei dati dopo lo split, tuttavia per i fini della nostra query ciò è del tutto irrilevante;
  • la DISTINCT permette di avere una query equivalente a quella con la IN anche nel caso fossero presenti dei duplicati all’interno dell’elenco di valori di lista_in
  • sp_executesql ci permette ancora una volta di evitare di costruire il codice SQL utilizzando i valori della variabile.

 

Continua a imparare

Torna su